Data transfer#

Abstracted APIs should attempt to hide the implementation details of the remote or local API in a well organized data model. This data model should avoid returning raw JSON files, gRPC messages, SWIG objects, or .NET objects. It should preferably return standard Python objects like lists, strings, dictionaries when useful, and numpy arrays or pandas dataframes for more complex data.

For example, consider a simple mesh in MAPDL:

mapdl.prep7()
mapdl.block(0, 1, 0, 1, 0, 1)
mapdl.et(1, 186)
mapdl.vmesh("ALL")

At this point, there are only two ways within MAPDL to access the nodes and connectivity of the mesh, You can either print it using the NLIST command or write it to disk using the CDWRITE command. Both of these methods are remarkably inefficient, requiring:

  • Serializing the data to ASCII on the server

  • Transferring the data

  • Deserializing the data within Python

  • Converting the data to an array

This example prints node coordinates using the NLIST command:

>>> print(mapdl.nlist())

NODE        X             Y             Z
 1   0.0000        1.0000        0.0000
 2   0.0000        0.0000        0.0000
 3   0.0000       0.75000        0.0000

It’s more efficient to transfer the node array as either a series of repeated Node messages or, better yet, to serialize the entire array into bytes and then deserialize it on the client side. For a concrete and standalone example of this in C++ and Python, see grpc_chunk_stream_demo.

While raw byte streams are vastly more efficient, one major disadvantage is that the structure of the data is lost when serializing the array. This should be considered when deciding how to write your API.

Regardless of the serialization or message format, users expect Python native types (or a common type for a common library like pandas.DataFrame or numpy.ndarray). Here, within PyMAPDL, the nodes of the mesh are accessible as the nodes attribute within the mesh attribute, which provides an encapsulation of the mesh within the MAPDL database.

>>> mapdl.mesh.nodes
array([[0.  , 1.  , 0.  ],
       [0.  , 0.  , 0.  ],
       [0.  , 0.75, 0.  ],
       ...
       [0.5 , 0.5 , 0.75],
       [0.5 , 0.75, 0.5 ],
       [0.75, 0.5 , 0.5 ]])

REST versus RPC data and model abstraction#

Because of the nature of most Ansys products, apps and services can either fit into the Remote Procedure Call (RPC) interface, where the API is centered around operations and commands, or the REST model, where the API is centered around resources. Regardless of the interface style, there are several items to consider.

API chattiness#

APIs must be efficient to avoid creating chatty input and output. Because many Ansys products fit well with the RPC API implementation, there is a temptation to design APIs that require constant communication with the remote or local service. However, just because RPC interfaces can do this, it does not mean that they should. The assumption should be that each RPC call takes significant time to execute.

One way to avoid constant communication with the service is to serialize several “commands” for services that employ an internal “command pattern” for data exchange. Another approach is to encapsulate the data model entirely on the server to avoid data transfer whenever possible and expose only a limited number of RPC methods in the front-facing API.

Compatibility and efficiency#

APIs should be designed to be compatible with as many languages and platforms as possible. gRPC for RPC-like interfaces should be one of the first choices due to its compatibility with nearly all popular languages and its efficiency over REST in terms of speed, memory, and payload size.

Typically, REST data exchanges should be limited to short messages that are transferred using JSON files, and gRPC should be used for large data transfers and bidirectional streaming.

Choosing gRPC over REST is generally preferable due to the performance benefits of a binary compatible protocol. While REST provides a variety of benefits, for complex client/server interactions, it is best to have an interface that can efficiently exchange a wide variety of data formats and messages.