0

Consider two facts of a typical organization:

  • Some entities are very common (e.g. Order in a POS system)
  • Some entities might have complex relations (e.g. Order might have a list of Items, a PaymentMethod, the Address of the customer and such)

Say the organization had a lot of (REST / json) micro-services and they all require data of similar kinds to be transferred between them.

E.g. service 1 might receive an Order from the client and then send that same order to service 2 in the course of fulfilling the request, before returning the PaymentMethdod to the client.

That means there's an opportunity to share the models between various parties (clients and services) involved, for cases where both are in the same language. Obviously if they are not in the same language (or at least not in the same underlying platform) that might not be doable. E.g. Java / Scala is possible, but Java / JavaScript might not be or it might incur additional costs of using a common descriptor and what not.

Let's assume these can be shared easily and that sharing would significantly decrease the time needed to create and maintain the same exact models in multiple places, potentially including the surrounding code (e.g. validation) as well. Let me know if you consider that not to be a valid assumption for some reason.

Some questions:

  • What, if any, are the negatives in sharing the model between parties?
  • If the model should not be shared, does that mean practically duplicating the same exact entities (or at least the required subsets) in each party or are there better approaches?
  • 2
    That's the hidden cost of REST and a microservices architecture, isn't it. If you start sharing, then don't you break the microservices design model? – Patrick Hughes Feb 27 at 20:29
  • 1
    Canonical data models are like the "One Ring" - one ring to bring them all and, in the darkness bind them. In other words It introduce coupling among independent services and why not, among their respective DLCs – Laiv Mar 4 at 21:52
4

Since you are asking about REST, then I assume that your "model" means a set of classes implementing common representations. In other words, you are asking if it is OK to share the source code of DTO classes for Json or XML serialization.

(If you are asking about entity/domain implementation, then absolutely DON'T DO THIS - the server and the client almost always have different assumptions about such simple concepts as "Order" and "Item".)

Here are some arguments against sharing the code of an API, (assuming the client and the server are owned by different teams):

  • The centralization of protocol specification is a dangerous illusion

    Even if your services are compiled daily against common-dtos-SNAPSHOT.jar, you don't control the order in which the services actually reach the production. Therefore, you should always think of forward and backwards compatibility - can a server talk to an old version of a client? Can a new client talk to an old server? Having a centralized place for the code creates an illusion for developers that all machines work in an idealised world.

  • Repositories with the common API code end up being owned by no team.

    There's a missing getter in a DTO - I can create a merge request, but there's literally noone in the organization who can merge it and relase a new version of the library.

  • The perceived maintanance benefits aren't real, they are more about aestethics

    Copying&pasting code between services feels ugly, but it takes like an hour max, where the total cost of maintaining various operational dependencies adds up unnoticeably.

Let me quote Sam Newman's "Building Microservices: Designing Fine-Grained Systems":

DRY and the Perils of Code Reuse in a Microservice World

One of the acronyms we developers hear a lot is DRY: don’t repeat yourself. Though its definition is sometimes simplified as trying to avoid duplicating code, DRY more accurately means that we want to avoid duplicating our system behavior and knowledge. This is very sensible advice in general. Having lots of lines of code that do the same thing makes your codebase larger than needed, and therefore harder to reason about. When you want to change behavior, and that behavior is duplicated in many parts of your system, it is easy to forget everywhere you need to make a change, which can lead to bugs. So using DRY as a mantra, in general, makes sense.

DRY is what leads us to create code that can be reused. We pull duplicated code into abstractions that we can then call from multiple places. Perhaps we go as far as making a shared library that we can use everywhere! This approach, however, can be deceptively dangerous in a microservice architecture.

One of the things we want to avoid at all costs is overly coupling a microservice and consumers such that any small change to the microservice itself can cause unnecessary changes to the consumer. Sometimes, however, the use of shared code can create this very coupling. For example, at one client we had a library of common domain objects that represented the core entities in use in our system. This library was used by all the services we had. But when a change was made to one of them, all services had to be updated. Our system communicated via message queues, which also had to be drained of their now invalid contents, and woe betide you if you forgot.

If your use of shared code ever leaks outside your service boundary, you have introduced a potential form of coupling. Using common code like logging libraries is fine, as they are internal concepts that are invisible to the outside world. RealEstate.com.au makes use of a tailored service template to help bootstrap new service creation. Rather than make this code shared, the company copies it for every new service to ensure that coupling doesn’t leak in.

My general rule of thumb: don’t violate DRY within a microservice, but be relaxed about violating DRY across all services. The evils of too much coupling between services are far worse than the problems caused by code duplication. There is one specific use case worth exploring further, though.

Client Libraries

I’ve spoken to more than one team who has insisted that creating client libraries for your services is an essential part of creating services in the first place. The argument is that this makes it easy to use your service, and avoids the duplication of code required to consume the service itself.

The problem, of course, is that if the same people create both the server API and the client API, there is the danger that logic that should exist on the server starts leaking into the client. I should know: I’ve done this myself. The more logic that creeps into the client library, the more cohesion starts to break down, and you find yourself having to change multiple clients to roll out fixes to your server. You also limit technology choices, especially if you mandate that the client library has to be used.

2

Sharing models between the client and the service in some cases are beneficial as it does require less code to write, less model to maintain. Also, you can implement consistency to a certain degree.

However, let's say that you are using an ORM i.e EntityFramework. You have an Order model that is shared between the client and service. Since you are most probably saving the Order model in the database, you would be using the auto-incremented Id property for CRUD operations. Does the client really care about the property Id? This can lead to vulnerabilities where the client would be having information that it shouldn't know about.

Another thing is the potential issue of scalability. You'll end up with the client and service tightly coupled because they are sharing the same model.

In addition, it would be a challenge to maintain the shared model between client and service if these applications sit in different projects, or are completely separate. You'd either be exporting this common model in a library or external package or some sort, therefore I can't really agree with your assumption.

Client and services are generally separate. Although this approach can potentially have its own disadvantage. example: if an API changes the model structure that it returns back to the client, this can lead to breaking change for the client. So does the same for the client sending a new property to the API (service) that it does not know.

At work, we have a client that maintains its own model so does the service (API) and we implement our own mapper that does the mapping between objects.

We've used other mapper libraries but they have their own drawbacks too.

0

I always share the models. Plus I hide the implementation of the service so that the calling code won't know that its making a REST call at all.

However. I don't put any methods on those models.

A soon as you add methods to the models things start to break down. Lets take an obvious example.

Order.Print()

My server side code is running on windows, checks the installed printers and triggers a print diaglogue.

If I retrieve an Order via a REST Api though, my code is now running on a completely separate machine. Its unlikely that the same printing code will work.

I probably want the print method to instead call the api's print end point.

Generally speaking, the Methods you wan't on the server side of the API are not the same as the Methods you want on the client side.

So if you are taking a classic OOP approach to your code and putting Methods on your Models, then you probably don't want to share the model.

If you take an ADM approach, all your methods are on Service Classes. So you can share the model all over the place, but the client will have a different PrintService to the server.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.