Sharing is caring
The other day I realised there is no real good way to share the same DTO models between your backend and your frontend even though that is the common code they need. Let us say an endpoint returns a simple representation of a user like a profile. That profile is needed to be displayed in the frontend again as well. Now if you code in full JavaScript you might get away with just accessing the properties or keys as you might just work with the JSON directly. However as things stand now, there is more and more TypeScript and therefore you need to redefine the same DTO in the frontend now before you can use it.
Next follow some options that are not necessarily ordered in any way although they all have flaws except for one that seems to be the nicest if you have a need to inject into an existing project.
OpenAPI / Swagger
You could create the OpenAPI spec for an API you are building and then use that specification to generate the necessary server and client code for however many languages you need. The problem with this is you need to maintain a static component that needs a dynamic action to keep things working. If you miss the action or only one party executes the generation then things get out of balance and it will be difficult to track down where it went wrong.
Having it as a reference is perfect or maybe if you have a third party vendor that cannot supply a SDK then this might be a sufficient alternative.
JSON / YAML
What if you have the models described in either JSON or YAML and use some sort of dynamic creation based on that? That might work but takes a lot of effort to maintain the actual solution that will create these classes dynamically. Also not all languages are suited for this approach, like for instance Java is not really suited to have dynamic creation of objects at runtime. There can be an annotation processor at compile time, however you cannot annotate a JSON file for Java to parse as if it were Java source code.
Clever individuals or people with the natural reaction of “that should be possible”, have no worries that it is possible to do this in Java. You can either run GraalJS or Rhino and directly interpret JavaScript code as Java code. This would however make the codebase excessively complex.
Protobufs / Thrift / Avro / FlatBuffers
There is also the option to have a schema definition and compile the DTO into a binary or even generate stubs and code. However this significantly changes your API design. This requires all sides to use the same framework and also makes it so you need to send binary data only. This does not have to be a problem but there are a lot of things that can go wrong when frameworks do not read binary data correctly but “stringify” it.
Avro supports reading the data and a schema so you can dynamically deserialize it. Similar to how XML has XSD. This however does mean a lot of boilerplate code in languages that do not really like dynamic stuff, like Java, but it might work. Frontend code might look funky dealing with all these UInt arrays to handle the binary data, yet it only needs to happen in one layer where you deal with the API.
From all the options so far I must say Avro sounds the most interesting option to try out and try to get in an existing project.
Node.js® / Deno + TypeScript
The last option I thought of was there exists the option to run the backend and the frontend in the same language these days; TypeScript. If you choose for the backend something like Node.js® or Deno written in TypeScript and for the frontend you choose any arbitrary framework that supports TypeScript you can have a folder in the middle that has your DTO definitions and both will use the same definition automatically. It is strange to me why not more projects are exactly like this. We seem to still rely on JSON to do the communication and therefore have to duplicate the DTO code constantly.