From 99f77f811978a71475485ac27ea386baf5a4b0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sun, 7 Apr 2024 18:44:04 +0300 Subject: [PATCH] docs: data transfer object + refactor --- data-transfer-object/README.md | 133 +++++++++--------- .../java/com/iluwatar/datatransfer/App.java | 14 +- .../datatransfer/customer/CustomerDto.java | 9 +- .../customer/CustomerResource.java | 19 +-- .../datatransfer/product/ProductResource.java | 40 +++--- .../customer/CustomerResourceTest.java | 25 ++-- 6 files changed, 112 insertions(+), 128 deletions(-) diff --git a/data-transfer-object/README.md b/data-transfer-object/README.md index 87252898b..96d4d286e 100644 --- a/data-transfer-object/README.md +++ b/data-transfer-object/README.md @@ -1,22 +1,28 @@ --- title: Data Transfer Object -category: Architectural +category: Structural language: en tag: - - Performance + - Client-server + - Data transfer + - Layered architecture + - Optimization --- +## Also known as + +* Transfer Object +* Value Object + ## Intent -Pass data with multiple attributes in one shot from client to server, to avoid multiple calls to -remote server. +The Data Transfer Object (DTO) pattern is used to transfer data between software application subsystems or layers, particularly in the context of network calls or database retrieval in Java applications. It reduces the number of method calls by aggregating the data in a single transfer. ## Explanation Real world example -> We need to fetch information about customers from remote database. Instead of querying the -> attributes one at a time, we use DTOs to transfer all the relevant attributes in a single shot. +> Imagine you're at a grocery store with a long shopping list. Instead of calling a friend from every aisle to ask what's needed, you compile the entire list into one message and send it over. This is akin to a Data Transfer Object (DTO) in software, where instead of making multiple requests for data, a single, compiled set of data (like the complete shopping list) is transferred in one go, optimizing communication and efficiency. In plain words @@ -24,97 +30,98 @@ In plain words Wikipedia says -> In the field of programming a data transfer object (DTO) is an object that carries data between -> processes. The motivation for its use is that communication between processes is usually done -> resorting to remote interfaces (e.g. web services), where each call is an expensive operation. -> Because the majority of the cost of each call is related to the round-trip time between the client -> and the server, one way of reducing the number of calls is to use an object (the DTO) that -> aggregates the data that would have been transferred by the several calls, but that is served by -> one call only. +> In the field of programming a data transfer object (DTO) is an object that carries data between processes. The motivation for its use is that communication between processes is usually done resorting to remote interfaces (e.g. web services), where each call is an expensive operation. Because the majority of the cost of each call is related to the round-trip time between the client and the server, one way of reducing the number of calls is to use an object (the DTO) that aggregates the data that would have been transferred by the several calls, but that is served by one call only. **Programmatic Example** -Let's first introduce our simple `CustomerDTO` class. +Let's first introduce our simple `CustomerDTO` record. ```java -public class CustomerDto { - private final String id; - private final String firstName; - private final String lastName; - - public CustomerDto(String id, String firstName, String lastName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - } - - public String getId() { - return id; - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } -} +public record CustomerDto(String id, String firstName, String lastName) {} ``` -`CustomerResource` class acts as the server for customer information. +`CustomerResource` record acts as the server for customer information. ```java -public class CustomerResource { - private final List customers; +public record CustomerResource(List customers) { - public CustomerResource(List customers) { - this.customers = customers; - } - - public List getAllCustomers() { - return customers; - } - - public void save(CustomerDto customer) { - customers.add(customer); - } - - public void delete(String customerId) { - customers.removeIf(customer -> customer.getId().equals(customerId)); - } + public void save(CustomerDto customer) { + customers.add(customer); + } + + public void delete(String customerId) { + customers.removeIf(customer -> customer.id().equals(customerId)); + } } ``` Now fetching customer information is easy since we have the DTOs. ```java - var allCustomers = customerResource.getAllCustomers(); - allCustomers.forEach(customer -> LOGGER.info(customer.getFirstName())); - // Kelly - // Alfonso +var customerOne = new CustomerDto("1", "Kelly", "Brown"); +var customerTwo = new CustomerDto("2", "Alfonso", "Bass"); +var customers = new ArrayList<>(List.of(customerOne, customerTwo)); +var customerResource = new CustomerResource(customers); +LOGGER.info("All customers:"); +var allCustomers = customerResource.getCustomers(); +printCustomerDetails(allCustomers); +``` + +The output will be: + +``` +18:31:53.868 [main] INFO com.iluwatar.datatransfer.App -- All customers: +18:31:53.870 [main] INFO com.iluwatar.datatransfer.App -- Kelly +18:31:53.870 [main] INFO com.iluwatar.datatransfer.App -- Alfonso ``` ## Class diagram -![alt text](./etc/data-transfer-object.urm.png "data-transfer-object") +![DTO class diagram](./etc/data-transfer-object.urm.png "data-transfer-object") ## Applicability Use the Data Transfer Object pattern when: -* The client is asking for multiple information. And the information is related. -* When you want to boost the performance to get resources. -* You want reduced number of remote calls. +* When you need to optimize network traffic by reducing the number of calls, especially in a client-server architecture. +* In scenarios where batch processing of data is preferred over individual processing. +* When working with remote interfaces, to encapsulate the data transfer in a serializable object that can be easily transmitted. ## Tutorials * [Data Transfer Object Pattern in Java - Implementation and Mapping](https://stackabuse.com/data-transfer-object-pattern-in-java-implementation-and-mapping/) * [The DTO Pattern (Data Transfer Object)](https://www.baeldung.com/java-dto-pattern) +## Known Uses + +* Remote Method Invocation (RMI) in Java, where DTOs are used to pass data across network. +* Enterprise JavaBeans (EJB), particularly when data needs to be transferred from EJBs to clients. +* Various web service frameworks where DTOs encapsulate request and response data. + +## Consequences + +Benefits: + +* Reduces network calls, thereby improving application performance. +* Decouples the client from the server, leading to more modular and maintainable code. +* Simplifies data transmission over the network by aggregating data into single objects. + +Trade-offs: + +* Introduces additional classes into the application, which may increase complexity. +* Can lead to redundant data structures that mirror domain models, potentially causing synchronization issues. +* May encourage design that leads to an anemic domain model, where business logic is separated from data. + +## Related Patterns + +* [Service Layer](https://java-design-patterns.com/patterns/service-layer/): Often involves using DTOs to transfer data across the boundary between the service layer and its clients. +* [Facade](https://java-design-patterns.com/patterns/facade/): Similar to DTO, a Facade may aggregate multiple calls into one, improving efficiency. +* [Composite Entity](https://java-design-patterns.com/patterns/composite-entity/): DTOs may be used to represent composite entities, particularly in persistence mechanisms. + ## Credits * [Design Pattern - Transfer Object Pattern](https://www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm) * [Data Transfer Object](https://msdn.microsoft.com/en-us/library/ff649585.aspx) * [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) * [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=014237a67c9d46f384b35e10151956bd) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cKndQp) diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java index e9164600c..ac96e53c4 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java @@ -67,15 +67,15 @@ public class App { var customerResource = new CustomerResource(customers); - LOGGER.info("All customers:-"); - var allCustomers = customerResource.getAllCustomers(); + LOGGER.info("All customers:"); + var allCustomers = customerResource.customers(); printCustomerDetails(allCustomers); LOGGER.info("----------------------------------------------------------"); LOGGER.info("Deleting customer with id {1}"); - customerResource.delete(customerOne.getId()); - allCustomers = customerResource.getAllCustomers(); + customerResource.delete(customerOne.id()); + allCustomers = customerResource.customers(); printCustomerDetails(allCustomers); LOGGER.info("----------------------------------------------------------"); @@ -83,7 +83,7 @@ public class App { LOGGER.info("Adding customer three}"); var customerThree = new CustomerDto("3", "Lynda", "Blair"); customerResource.save(customerThree); - allCustomers = customerResource.getAllCustomers(); + allCustomers = customerResource.customers(); printCustomerDetails(allCustomers); // Example 2: Product DTO @@ -131,10 +131,10 @@ public class App { productResource.save(createProductRequestDto); LOGGER.info( "####### List of products after adding PS5: {}", - Arrays.toString(productResource.getProducts().toArray())); + Arrays.toString(productResource.products().toArray())); } private static void printCustomerDetails(List allCustomers) { - allCustomers.forEach(customer -> LOGGER.info(customer.getFirstName())); + allCustomers.forEach(customer -> LOGGER.info(customer.firstName())); } } diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java index 556adae0e..402075e4d 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java @@ -33,11 +33,4 @@ import lombok.RequiredArgsConstructor; * *

Dto will not have any business logic in it. */ -@Getter -@RequiredArgsConstructor -public class CustomerDto { - private final String id; - private final String firstName; - private final String lastName; - -} +public record CustomerDto(String id, String firstName, String lastName) {} diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java index f45509b00..0fcae3e91 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java @@ -25,25 +25,14 @@ package com.iluwatar.datatransfer.customer; import java.util.List; +import lombok.Getter; import lombok.RequiredArgsConstructor; /** * The resource class which serves customer information. This class act as server in the demo. Which * has all customer details. */ -@RequiredArgsConstructor -public class CustomerResource { - private final List customers; - - /** - * Get all customers. - * - * @return : all customers in list. - */ - public List getAllCustomers() { - return customers; - } - +public record CustomerResource(List customers) { /** * Save new customer. * @@ -59,6 +48,6 @@ public class CustomerResource { * @param customerId delete customer with id {@code customerId} */ public void delete(String customerId) { - customers.removeIf(customer -> customer.getId().equals(customerId)); + customers.removeIf(customer -> customer.id().equals(customerId)); } -} \ No newline at end of file +} diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java index 9adafc8be..dc40f0688 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java @@ -30,16 +30,13 @@ import java.util.List; * The resource class which serves product information. This class act as server in the demo. Which * has all product details. */ -public class ProductResource { - private final List products; - +public record ProductResource(List products) { /** * Initialise resource with existing products. * * @param products initialize resource with existing products. Act as database. */ - public ProductResource(final List products) { - this.products = products; + public ProductResource { } /** @@ -49,11 +46,11 @@ public class ProductResource { */ public List getAllProductsForAdmin() { return products - .stream() - .map(p -> new ProductDto.Response.Private().setId(p.getId()).setName(p.getName()) - .setCost(p.getCost()) - .setPrice(p.getPrice())) - .toList(); + .stream() + .map(p -> new ProductDto.Response.Private().setId(p.getId()).setName(p.getName()) + .setCost(p.getCost()) + .setPrice(p.getPrice())) + .toList(); } /** @@ -63,10 +60,10 @@ public class ProductResource { */ public List getAllProductsForCustomer() { return products - .stream() - .map(p -> new ProductDto.Response.Public().setId(p.getId()).setName(p.getName()) - .setPrice(p.getPrice())) - .toList(); + .stream() + .map(p -> new ProductDto.Response.Public().setId(p.getId()).setName(p.getName()) + .setPrice(p.getPrice())) + .toList(); } /** @@ -76,12 +73,12 @@ public class ProductResource { */ public void save(ProductDto.Request.Create createProductDto) { products.add(Product.builder() - .id((long) (products.size() + 1)) - .name(createProductDto.getName()) - .supplier(createProductDto.getSupplier()) - .price(createProductDto.getPrice()) - .cost(createProductDto.getCost()) - .build()); + .id((long) (products.size() + 1)) + .name(createProductDto.getName()) + .supplier(createProductDto.getSupplier()) + .price(createProductDto.getPrice()) + .cost(createProductDto.getCost()) + .build()); } /** @@ -89,7 +86,8 @@ public class ProductResource { * * @return : all the products entity that stored in the products list */ - public List getProducts() { + @Override + public List products() { return products; } } diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java index cc14c059a..1a90a15e1 100644 --- a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java @@ -29,8 +29,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; -import com.iluwatar.datatransfer.customer.CustomerDto; -import com.iluwatar.datatransfer.customer.CustomerResource; import org.junit.jupiter.api.Test; /** @@ -42,12 +40,12 @@ class CustomerResourceTest { void shouldGetAllCustomers() { var customers = List.of(new CustomerDto("1", "Melody", "Yates")); var customerResource = new CustomerResource(customers); - var allCustomers = customerResource.getAllCustomers(); + var allCustomers = customerResource.customers(); assertEquals(1, allCustomers.size()); - assertEquals("1", allCustomers.get(0).getId()); - assertEquals("Melody", allCustomers.get(0).getFirstName()); - assertEquals("Yates", allCustomers.get(0).getLastName()); + assertEquals("1", allCustomers.get(0).id()); + assertEquals("Melody", allCustomers.get(0).firstName()); + assertEquals("Yates", allCustomers.get(0).lastName()); } @Test @@ -57,10 +55,10 @@ class CustomerResourceTest { customerResource.save(customer); - var allCustomers = customerResource.getAllCustomers(); - assertEquals("1", allCustomers.get(0).getId()); - assertEquals("Rita", allCustomers.get(0).getFirstName()); - assertEquals("Reynolds", allCustomers.get(0).getLastName()); + var allCustomers = customerResource.customers(); + assertEquals("1", allCustomers.get(0).id()); + assertEquals("Rita", allCustomers.get(0).firstName()); + assertEquals("Reynolds", allCustomers.get(0).lastName()); } @Test @@ -69,10 +67,9 @@ class CustomerResourceTest { var customers = new ArrayList<>(List.of(customer)); var customerResource = new CustomerResource(customers); - customerResource.delete(customer.getId()); + customerResource.delete(customer.id()); - var allCustomers = customerResource.getAllCustomers(); + var allCustomers = customerResource.customers(); assertTrue(allCustomers.isEmpty()); } - -} \ No newline at end of file +}