Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

Microservices have become popular in recent years. One of the essential characteristics of microservices is that they are modular, isolated, and easy to scale. The microservices need to work together and exchange data. To achieve this, we create a shared data transfer objects called as DTOs.

In this article, we will present ways in which DTOs are shared between microservices.

2. Exposing Domain Objects as DTO

Models that represent the application domain are managed using microservices. Domain models are different concerns, and we separate them from data models in the DAO layer.

The main reason for this is that we don't want to expose the complexity of our domain through the services to the clients. Instead, we expose DTOs between our services that serve application clients through REST APIs. While DTOs pass between these services, we convert them to domain objects.

The service-oriented architecture above schematically shows the components and flow of DTO to Domain objects.

3. DTO Sharing Between Microservices

Take, as an example, the process of a customer ordering a product. This process is based on the Customer-Order model. Let's look at the process from the side of the service architecture.

Let's say that the Customer service sends request data to the Order service as:

"order": {
    "customerId": 1,
    "itemId": "A152"
}

The Customer and Order services communicate with each other using contracts. The contract, which is otherwise a service request, is displayed in JSON format. As a Java model, the OrderDTO class represents a contract between the Customer service and the Order service:

public class OrderDTO {
    private int customerId;
    private String itemId;

    // constructor, getters, setters
}

3.1. Sharing DTO Using Client Modules (Libraries)

A microservice requires certain information from other services to process any request. Let's say there is a third microservice that receives order payment requests. Unlike the Order service, this service requires different customer information:

public class CustomerDTO {
    private String firstName;
    private String lastName;
    private String cardNumber;

    // constructor, getters, setters
}

If we also add a delivery service, customer information would have:

public class CustomerDTO {
    private String firstName;
    private String lastName;
    private String homeAddress;
    private String contactNumber;

    // constructor, getters, setters
}

So, placing the CustomerDTO class in a shared module no longer serves the intended purpose. To solve this, we approach a different method.

Within each microservice module, let's create a client module (library) and next to it a server module:

order-service
|__ order-client
|__ order-server

The order-client module contains a DTO shared with Customer service. Therefore, the order-client module has the following structure:

order-service
└──order-client
     OrderClient.java
     OrderClientImpl.java
     OrderDTO.java

The OrderClient is an interface that defines an order method for processing order requests:

public interface OrderClient {
    OrderResponse order(OrderDTO orderDTO);
}

To implement the order method, we use the RestTemplate object to send a POST request to the Order service:

String serviceUrl = "http://localhost:8002/order-service";
OrderResponse orderResponse = restTemplate.postForObject(serviceUrl + "/create", 
  request, OrderResponse.class);

Besides, the order-client module is ready for use. It now becomes a dependent library of the customer-service module:

[INFO] --- maven-dependency-plugin:3.1.2:list (default-cli) @ customer-service ---
[INFO] The following files have been resolved:
[INFO]    com.baeldung.orderservice:order-client:jar:1.0-SNAPSHOT:compile

Of course, this has no purpose without the order-server module to expose the “/create” service endpoint to the order client:

@PostMapping("/create")
public OrderResponse createOrder(@RequestBody OrderDTO request)

Thanks to this service endpoint, the Customer service can send an order request through its order client. By using the client module, microservices communicate with each other in a more isolated way. Attributes in the DTO are updated within the client module. Therefore, contract breaking is limited to services that use the same client module.

4. Conclusion

In this article, we explained a way to share DTO objects between microservices. At best, we achieve this by making special contracts as parts of microservice client modules (libraries). In this way, we separate the service client from the server part that contains the API resource. As a result, there are some benefits:

  • There is no redundancy in the DTO code between services
  • Contract breaking is limited to services that use the same client library

A code sample of a Spring Boot application is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
guest
10 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jeff
Jeff
9 days ago

Yuck, this is a horrible practice and adds coupling to your microservices.

Loredana Crusoveanu
9 days ago
Reply to  Jeff

Hi Jeff,
In an ideal microservices world, each service would be totally independent of the other. But in practice compromises are sometimes necessary, and there could be situations where one service depends on data from another service. This article aims to present a way to do that.

Cheers!

Ant Kutschera
Ant Kutschera
9 days ago

Sharing dtos only makes sense if you have made the services too small or are building a distributed monolith. Neither are recommended.

Loredana Crusoveanu
Reply to  Ant Kutschera

Hi Ant,

Thanks for the feedback. We’ve updated the article and removed the shared-dto section, to focus on the client library approach.

Constantin
Constantin
9 days ago

There are multiple problems with this approach: 1) changing the dto in one service ends up breaking the build on the other service, you’ve essential build a connected codebase 2) by connecting your codebase you’ve created a distributed monolith. The only upside is that you can scale parts of your application independently. (If you just want this, go for it) 3) you lock yourself in with one language 4) If you go by versioning your dto modules, you have to deal with that too (can be annoying) The language lock-in is probably not a very convincing argument, because most product… Read more »

Loredana Crusoveanu
Reply to  Constantin

Hi Constantin,

Thanks for the feedback. We’ve updated the article to remove the first approach using the shared-dto module, and focus on the client libraries approach.
We’ll look into the swagger-codegen suggestion.

Julio Marques
Julio Marques
9 days ago

You are just creating a distributed monolith. Micro Services must be single individuals for a reason.

Loredana Crusoveanu
Reply to  Julio Marques

Thanks for the feedback, Julio. The article is updated to remove the first approach using the shared-dto module, and focuses on the client libraries approach.

enayet-repo
enayet-repo
9 days ago

How about using swagger code gen with DTO specification for autocreation? Seems simpler than these approaches.

Loredana Crusoveanu
Reply to  enayet-repo

Thanks for the suggestion. We’ll update the article.