19

TL;DR - Is it ok to share a POJO library between services?

Generally we like to keep the sharing between services strictly limited to none if possible. There has been some debate whether or not the service that is sharing data should provide a client-library for clients to use. The client-lib is generally optional for a client of the service to use and can consume the API however they please, whether to use the client-lib, or use an alternative language and use the general aspects of the library and such.

In my case - I am consider a service that create an object of data. Let's assume this object is a PET. It is NOT the database entity, but strictly a POJO that implicitly represents the underlying data. This POJO is what the API has defined. Assume: Pet - Age, Weight, Name, Owner, Address, Species, etc.

Service 1 - PetKeeper: It will generate a pet for whatever reason and retain all of the data and must reference this service to obtain the pet, or make modifications to the Pet, lets say the name changes, or address change must be done through an API call to this service.

Service 2 - PetAccessor: This services gathers the pet's and does validation checks

Service 3,4 - More intermediate service calls

Service 5 - User Inteface

These are very arbitrary but the point is simple. The UI or some user-facing service wishes to present in some way this "PET" object. It must call through an API a service, which calls a service, which calls a service, etc until it reaches the service which gathers the required information and begins the relay back. Finally the UI service has the PET object to display.

This is pretty common - but with our absolute mentality, we duplicated the PET object in every service. DRY (don't repeat yourself) principle only applies to code INSIDE a service and doesn't apply across services but the point is still there. What if we add a field... we must modify 5 services of the POJO in each.

--OR-- We can provide a Pet-Objects-Library which contains some of the pojo's from the API and each service can import/dependency on the library. There is no dependency on the service(s) themselves, but just the general library. I like this idea so that each service has the same type of object and updates are easier. But I'm concerned about God-Objects.

What are the pro's/con's - what's the best design? What have you done to pass data between services to minimize repeating the same POJO classes while also staying de-coupled?

5
  • God-Object? en.wikipedia.org/wiki/God_object
    – Dai Kaixian
    Nov 28, 2016 at 7:00
  • @DaiKaixian: Surely you're not suggesting that the OP go with a God object, are you? That's routinely considered an anti-pattern.
    – Makoto
    Nov 28, 2016 at 7:01
  • I agree with @javaguy 's answer.
    – Dai Kaixian
    Nov 28, 2016 at 7:08
  • And I also want to say , you can consider visitor-pattern .en.wikipedia.org/wiki/Visitor_pattern . Make all the field and setter/getter in a POJO, and share it between microservices.If you want to do some operation on the POJO in different microservice, write some VisitorClass.
    – Dai Kaixian
    Nov 28, 2016 at 7:16
  • Thanks. My hesitation with having this 'common library' is that it will grow. And there will be objects in there that only services 1 & 3 care about, or 2 & 4, or all, or any combination there of. A type of general DTO library package that has all DTO's whether I use a vistor pattern or simple DTO POJO or what-not. Is this acceptable to include all these objects but to try to maintain it as best as possible? At least the objects are provided to any who need them IF they wish to use them...
    – Aerith
    Nov 28, 2016 at 8:12

5 Answers 5

6

What's the best design?

You can reuse the same Pet DTO object among the backend services (which process the typical business logic), but when it comes to the presentation tier (User Interface), it is generally a good practice to use a FormBean (a different bean with added fields for presentation logic) so that there will be a clear separation between presentation logic and business logic.

This is required because services should be reusable and a single service can be exposed/reused by multiple/different endpoints (like frontend or could be a different webservice, etc.. ) and each of those endpoints might require additional fields which will be populated by the respective Controllers or layers (like adapters) above the services.

What have you done to pass data between services to minimize repeating the same POJO classes while also staying de-coupled ?

If you use a single bean between business and web tiers then you are tightly coupling the presentation logic with business logic which is not good practice and you will end up changing the services for a requirement in the Frontend (like for example, a different date format to be shown in User Interface). Also, to make this process of populating/copying the data across the beans (like DTO to FormBean or Viceversa), you can use libraries like Apache BeanUtils.copyProperties() or Dozer to avoid the boilerplate code.

2
  • I agree the presentation layer should probably deserialize the incoming payload as a presentation bean with different attributes as needed. But in general is it ok to reuse the same DTO object(s) in all the backend services? We would generally try to separate out these DTO packages in smaller packages for only the few services that need them. I'm weary of having some junk-drawer of a DTO library package that has DTO's for some 75+ microservices that all services depend on. Unless that's ok since it's just DTO objects that are optional in the first place?
    – Aerith
    Nov 28, 2016 at 8:13
  • Yes, you can obviously reuse the same DTO object across all same kind of backend services like your PetServices to avoid duplication. But my point is don't tightly couple backend and frontend, that's it.
    – Vasu
    Nov 28, 2016 at 8:37
4

If the DTO represents the same business entity in all the microservices there should be only one class, shared among the services. It's (almost) never correct to have duplicate code for the same object.

1
  • 5
    Sharing DTOs across microservices is a nightmare. "Does this version has already this field? Hm maybe?" You will end up with a real mess after a while. Duplicated code is good in this case.
    – Mejmo
    Oct 25, 2018 at 22:21
4

The way how I plan to do it now, is that each service packages only DTOs and put them into Nexus as jar lib. When other service need those, it will get thos DTO lib(s) as dependency in manve/gradle. If new DTO version is released on one service, its fine as long as old version is also supported at the same time, so don't break backwards compatibility, versioning, etc.. so this is backend-to-backend area. Also to prevent circural dependency you are better to separate service from dto packaging

Now look at backend-to-frontend and vice versa I disagree with previous comments that UI as presentation layer is different. IT IS NOT!!! UI is just another microservice to me which also consumes and produces events.

Backend-to-frontend direction What I do is to convert POJO(dtos) to Typescript interfaces and package into NPM and load them into Nexus as well. UI nodejs based project then consumes and uses those. This is way service to UI.

Frontend-to-backend direction For UI to service layer events I convert Typescript interfaces and convert them to POJOs(dtos), package as jar and upload to Nexus (or some repo) in form of jar to be consumed by backend services.

These processes are easily handled by CI processes (Travis, Gitlab CI, etc.)

Any comments to this approach welcomed.

1
  • 1
    Treat POJO as jar lib and include it as dependency sounds like the right approach.
    – lennon310
    Nov 22, 2020 at 1:43
3

Neither. Your services aren't microservices.

You have unfortunately created a "distributed monolith" or "hidden monolith".

  1. Your services aren't loosely coupled. A change in one service will probably result in a change in another service. Often this will require simultaneous deployment of multiple services.
  2. Your services aren't loosely coupled. You require synchronous communication between services. If one service is down then all the services above it are down (and as a bonus the services below it might as well be down).
  3. Your services aren't loosely coupled. They won't scale independently, if you have a spike in traffic in one service you will see that reflected in every service because they all call each other.

You should (sorry) go back to square one. Reconsider if you even need microservices or if your goals can still be served by a monolith. If you decide that you do need microservices then have another go at extracting them looking for business capabilities, bounded contexts, data-rich services, etc.

Once you've done that then you shouldn't have much/any need to share these objects around anyway, so your problem will be solved.

2

The challenges with shared code between services is merely the good old question about dependencies.

There is no right or wrong here, but a balance and tradeoffs for the path you choose. And this path I would argue is quiet significant in software development.

A shared libary is a dependency. Dependencies can have a bad rep' because they have a tendency to lock-in your software agility. When you try to get out of that lock-in you will find that suddenly you have a "rogue implementation" which might not adhere to whatever that prior dependency exposed - but your implementation will be free.

A general rule, and a good one, is to have each service own its own implementation. But a lot of developers is really bad at coping with this on a mental level, because they believe shared code is about reuse. But when a dependent service cannot reuse due to perhaps a small tweak, that dependency can either become irrelevant or grow (because now it must also adhere to that little tweak).

So services which has the same dependency most often "locks" the dependency because know its being used by things you have no control over, and no one can guarentee it will never evolve. But when it evolves it becomes more difficult to maintain - and this will most likely also cause challenges for dependent services.

A dependency often ends up growing bigger over time. Because it evolves. And then versions come around. Then breaking changes. And then new dependencies come around. So dependencies becomes centerpieces of your services - and that is not what you want.

Remember the rule - the service owns its implementation. Especially DTO's because they are likely to not be stale over time. Especially in the beginning of a software project!

Another thing I have seen over and over again, is that DTO properties are very rarely reused to the fullest.

One service might want to use the FullName property where another service only needs the Email of a DTO. That's not reuse. That's a different need. Hence DRY does not apply. DRY is only about 100% reuse!!!

Also, the DRY principle is great but remember service domain contexts and where the responsibility lies.

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