Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4535

How to Share DTO Across Microservices

$
0
0

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. Usual Methods of Sharing DTO

In practice, software developers typically copy DTOs from one microservice to another. This leads to having redundancy in our microservice design. A better and more logical solution is to create a shared-dto module:

shared-dto
|__ CustomerDTO.java
|__ OrderDTO.java

This method is better because there is no redundancy, and DTO attributes are easy to change. A shared-dto module has its function if several microservice modules share the same DTO class.

There is one disadvantage of this method. What if the CustomerDTO class is not always the same for additional microservices in our design (such as payment and delivery services)? Changing the CustomerDTO class causes a service that already uses the DTO to stop working.

3.2. 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 several ways 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.


Viewing all articles
Browse latest Browse all 4535

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>