1. Fund Adaptor
Suppose that Fund Adaptor executes a service to search the response data- a monthly or daily (depending on the schedule). This example is to show how the customer details response data is mapped. There are two data transfer objects (DTO) namely CustomerDTO (maybe to present in UI) and CustomerSvcDTO (from executing store procedures or calling services). Moreover, the data responded should support paging.
2. Solution
– Use ModelMapper to convert one model to another, which makes it possible for us to maintain, refactor, implement easily and quickly.
– Define Pagination that support previous, next, first, last, to name just a few.
3. Design.
3.1 Class diagram
Figure 1: A part of class diagram for model mapper
3.2 Project structure
Figure 2: Data (so-called message) response project structure.
4. Implementation.
4.1 DTOs.
src/main/java/com/fund/adaptor/message/common/Identifiable.java
package com.fund.adaptor.message.common; import java.io.Serializable; public interface Identifiable extends Serializable { }
src/main/java/com/fund/adaptor/message/common/ToStringAllFieldsSupport.java
package com.fund.adaptor.message.common; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; public class ToStringAllFieldsSupport { private static final String[] EXCLUDED_FIELD_NAMES = new String[]{"password"}; public ToStringAllFieldsSupport() { } public String toString() { return (new ReflectionToStringBuilder(this)) .setExcludeFieldNames(EXCLUDED_FIELD_NAMES).toString(); } }
src/main/java/com/fund/adaptor/message/common/DTO.java
package com.fund.adaptor.message.common; import net.logstash.logback.encoder.org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; public class DTO extends ToStringAllFieldsSupport implements Identifiable { public DTO() { } public boolean equals(Object other) { // This method uses reflection to determine if the two Objects are equal. return EqualsBuilder.reflectionEquals(this, other, false); } public int hashCode() { return HashCodeBuilder.reflectionHashCode(this, new String[0]); } }
src/main/java/com/fund/adaptor/message/common/SearchResultPageLinksDTO.java
package com.fund.adaptor.message.common; public class SearchResultPageLinksDTO extends DTO { private String previous; private String next; private String first; private String last; // Add default constructor, setter and getter, equal and hashCode. }
src/main/java/com/fund/adaptor/message/common/PagingResultDTO.java
package com.fund.adaptor.message.common; import java.util.ArrayList; import java.util.List; public abstract class PagingResultDTO <D extends DTO> { private List<D> results = new ArrayList(); private Integer offset; private Integer total; private Integer limit; public PagingResultDTO() { } // Add setter and getter. }
src/main/java/com/fund/adaptor/message/common/UIPagingResultsDTO.java
package com.fund.adaptor.message.common; public class UIPagingResultsDTO <D extends DTO> extends PagingResultDTO<D> { private SearchResultPageLinksDTO links = new SearchResultPageLinksDTO(); public UIPagingResultsDTO() { } public SearchResultPageLinksDTO getLinks() { return links; } public void setLinks(SearchResultPageLinksDTO links) { this.links = links; } }
src/main/java/com/fund/adaptor/message/dto/CustomerSvcDTO.java
package com.fund.adaptor.message.dto; import com.fund.adaptor.message.common.DTO; import org.joda.time.LocalDateTime; /** * DTO response data from a micro-service. */ public class CustomerSvcDTO extends DTO { private Integer customerNum; private String trustInvestorName; private String addressStreet; private String addressCity; private String addressCountry; private LocalDateTime dateOfBirth; private Integer returnCode; // Add constructor with fields, setter and getter, equals and hashCode }
src/main/java/com/fund/adaptor/message/dto/CustomerDTO.java
package com.fund.adaptor.message.dto; import com.fund.adaptor.message.common.DTO; import org.joda.time.LocalDateTime; import javax.validation.constraints.NotNull; /** * DTO presentation. */ public class CustomerDTO extends DTO { @NotNull private Integer custNum; private String name; private String street; private String city; private String country; private LocalDateTime dob; private Integer returnCode; // Add constructor with fields, setter and getter, equals and hashCode }
src/main/java/com/fund/adaptor/message/dto/CustomerSvcSearchResultsDTO.java
package com.fund.adaptor.message.dto; import com.fund.adaptor.message.common.PagingResultDTO; public class CustomerSvcSearchResultsDTO extends PagingResultDTO<CustomerSvcDTO> { }
src/main/java/com/fund/adaptor/message/dto/CustomerSearchResultsDTO .java
package com.fund.adaptor.message.dto; import com.fund.adaptor.message.common.UIPagingResultsDTO; public class CustomerSearchResultsDTO extends UIPagingResultsDTO<CustomerDTO> { }
4.2 Model Mapper
src/main/java/com/fund/adaptor/message/dto/CustomerMapper.java
package com.fund.adaptor.message.dto; import org.springframework.stereotype.Component; import java.lang.reflect.Type; import java.util.List; import org.modelmapper.ModelMapper; import org.modelmapper.PropertyMap; import org.modelmapper.TypeToken; /** * Customer Message Mapper. */ @Component public class CustomerMapper { public ModelMapper modelMapper = new ModelMapper(); public CustomerMapper() { modelMapper.addMappings(new ToCustomerSvcDTOMapping()); modelMapper.addMappings(new ToCustomerDTOMapping()); } /** Mapping from CustomerSvcDTo to CustomerDTO **/ public CustomerDTO toCustomerDTO(CustomerSvcDTO customerSvcDTO) { return modelMapper.map(customerSvcDTO, CustomerDTO.class); } /** Mapping from CustomerDTO to CustomerSvcDTO **/ public CustomerSvcDTO toCustomerSvcDTO(CustomerDTO customerDTO) { return modelMapper.map(customerDTO, CustomerSvcDTO.class); } /** Mapping from CustomerSvcDTO List to CustomerDTO List **/ public List<CustomerDTO> toCustomerDTOList(List<CustomerSvcDTO> customerSvcDTOList) { Type listType = new TypeToken<List<CustomerDTO>>() {}.getType(); List<CustomerDTO> customerDTOs = modelMapper.map(customerSvcDTOList, listType); return customerDTOs; } static class ToCustomerSvcDTOMapping extends PropertyMap<CustomerDTO, CustomerSvcDTO> { /** Define mapping configure PropertyMap <Source, Destination> **/ @Override protected void configure() { map().setCustomerNum(source.getCustNum()); map().setTrustInvestorName(source.getName()); map().setAddressStreet(source.getStreet()); map().setAddressCity(source.getCity()); map().setAddressCountry(source.getCountry()); map().setDateOfBirth(source.getDob()); skip().setReturnCode(null); } } static class ToCustomerDTOMapping extends PropertyMap<CustomerSvcDTO, CustomerDTO> { /** Define mapping configure PropertyMap <Source, Destination> **/ @Override protected void configure() { map().setCustNum(source.getCustomerNum()); map().setName(source.getTrustInvestorName()); skip().setStreet(null); skip().setCity(null); skip().setCountry(null); skip().setDob(source.getDateOfBirth()); map().setReturnCode(source.getReturnCode()); } } }
4.3 Service
src/main/java/com/fund/adaptor/message/service/CustomerMessageService.java
package com.fund.adaptor.message.service; import com.fund.adaptor.message.dto.CustomerSearchResultsDTO; public interface CustomerMessageService { CustomerSearchResultsDTO getCustomerMessage(String someStuff, Integer offset, Integer limit); }
src/main/java/com/fund/adaptor/message/service/CustomerMessageService.java
package com.fund.adaptor.message.service; import com.fund.adaptor.message.dto.*; import com.fund.adaptor.message.gateway.CustomerMessageGateway; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import static java.util.stream.Collectors.toList; @Service public class CustomerMessageServiceImpl implements CustomerMessageService{ @Autowired private CustomerMessageGateway customerMessageGateway; @Autowired private CustomerMapper customerMapper; /** * Get Customer Message with paging. * @param someStuff put your parameters * @param offset page number. * @param limit number of records per page. * @return CustomerSearchResultsDTO */ public CustomerSearchResultsDTO getCustomerMessage(String someStuff, Integer offset, Integer limit) { CustomerSvcSearchResultsDTO customerSvcSearchResults = customerMessageGateway.getCustomerMessageResponse(someStuff, offset, limit); CustomerSearchResultsDTO results = new CustomerSearchResultsDTO(); final List<CustomerDTO> customerDTOs = customerSvcSearchResults.getResults() .stream() .map(customerSvcDTO -> customerSvcDTOToCustomerDTO(customerSvcDTO, 1)) //.map(this::customerSvcDTOToCustomerDTO) .collect(toList()); results.setResults(customerDTOs); results.setOffset(customerSvcSearchResults.getOffset()); results.setLimit(customerSvcSearchResults.getLimit()); results.setTotal(customerSvcSearchResults.getTotal()); return results; } private CustomerDTO customerSvcDTOToCustomerDTO( CustomerSvcDTO customerSvcDTO,int messageCode) { CustomerDTO customerDTO = customerMapper.toCustomerDTO(customerSvcDTO); /** If message code #1 unsuccessful execution, then update message code #2 - do more action**/ if(customerDTO.getReturnCode() == messageCode) { customerDTO.setReturnCode(2); } return customerDTO; } private CustomerDTO customerSvcDTOToCustomerDTO(CustomerSvcDTO customerSvcDTO) { return customerMapper.toCustomerDTO(customerSvcDTO); } }
4.4 Application executable
src/main/java/com/fund/adaptor/message/Application.java
package com.fund.adaptor.message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PostConstruct; import java.io.IOException; @Configuration @ComponentScan(basePackages = {"com.fund.adaptor.message"}) @EnableAutoConfiguration public class Application { private static final Logger LOG = LoggerFactory.getLogger(Application.class); @Autowired private Environment env; @PostConstruct public void initApplication() throws IOException { if (env.getActiveProfiles().length == 0) { LOG.warn("No Spring profile configured, running with default configuration"); } else { LOG.info("Running with Spring profile(s) : {}", env.getActiveProfiles()); } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
4.5 pom.xml
message-response-mapping/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.fund.adaptor</groupId> <artifactId>message-reponse-mapping</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>message-reponse-mapping</name> <url>http://maven.apache.org</url> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>1.2.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.1.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.2.RELEASE</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.4.Final</version> </dependency> <!-- Module Mapping --> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>0.7.5</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>4.2</version> </dependency> <!-- Utils --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.4</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.3</version> </dependency> <!-- Testing --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> </dependencies> </project>
5. Testing
src/test/java/com/fund/adaptor/message/mapper/CustomerMapperTest .java
package com.fund.adaptor.message.mapper; import org.junit.Test; import com.fund.adaptor.message.dto.CustomerMapper; public class CustomerMapperTest { @Test public void validateMappingTest(){ new CustomerMapper().modelMapper.validate(); } }
src/test/java/com/fund/adaptor/message/service/CustomerServiceITTest.java
package com.fund.adaptor.message.service; import com.fund.adaptor.message.Application; import com.fund.adaptor.message.dto.CustomerDTO; import com.fund.adaptor.message.dto.CustomerSearchResultsDTO; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebAppConfiguration @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @ActiveProfiles("devmock") public class CustomerServiceITTest { @Autowired private CustomerMessageService customerMessageService; @Test public void getCustomerMessageResponseTest() { CustomerSearchResultsDTO resultsDTO = customerMessageService .getCustomerMessage("", 0, 5); Assert.assertNotNull(resultsDTO); Assert.assertEquals(4, resultsDTO.getTotal().intValue()); Assert.assertNotNull(resultsDTO.getResults()); List<CustomerDTO> customerDTOList = resultsDTO.getResults(); customerDTOList.forEach(customerDTO -> { if(8082 == customerDTO.getCustNum().intValue()) { Assert.assertEquals("Return MessageCode expected is 2", 2, customerDTO.getReturnCode().intValue()); } else { Assert.assertEquals("Return MessageCode expected is 0", 0, customerDTO.getReturnCode().intValue()); } }); } }
References:
- ModelMapper Accessed at http://modelmapper.org/
- ReflectionToStringBuilder, Accessed at https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/ReflectionToStringBuilder.html
- Spring Boot, Pivotal Software. Accessed at http://projects.spring.io/spring-boot/#quick-start
Alternately, Lightweight model mapping with Java 8