Author Archives: yogesh.mali@gmail.com

How To Use AWS Sagemaker

In this post, I will explain how to use AWS Sagemaker. Even if you do not have experience with this AWS service, this guide will help understand AWS Sagemaker step by step.

What is AWS Sagemaker?

AWS Sagemaker is a new web service that AWS offers. It helps to build, train and deploy machine learning models at any scale.  Basically, Sagemaker does the heavy lifting of machine learning and as a developer or data scientist, you can focus on building and training your model.

Major Benefits of AWS Sagemaker

  • You can easily fetch or store data from other AWS Services
  • Highly Scalable. This again relates to my earlier point by being able to connect to other AWS Services.
  • Does heavy lifting of ML algorithms – Fast training

Details Of Machine Learning and Sagemaker

Machine learning is literally machine learning about something. Nevertheless, that something can be anything that humans are usually good at or bad at. Machine Learning provides an ability for systems to learn and improve from experience.

In another way, you can say a system with a feedback loop. A system performs functions, gathers data along the way, uses that data to improve the functions it is performing.

Building a model

Sagemaker makes it easy to connect with AWS Services like S3, Database. Sagemaker also includes Juypter notebooks. These notebooks make it easier to visualize data.

Sagemaker also offers a set of algorithms pre-installed. Sagemaker also comes in with preconfigured TensorFlow or Apache MXNet.

Training and Deploying a model

I will show later in this post how we can train a model in Sagemaker with a single click. The important thing to note here is that you can easily train a model for petabyte-scale in Sagemaker. With continuous improvement, Sagemaker can also improve the performance of the model.

Once you train and tune the model in Sagemaker, it is easy to deploy the model in production. Sagemaker deploys the model on an auto-scaling cluster of EC2 instances.

A simple example of using AWS Sagemaker

  1. Once, you log in to the AWS console, access Sagemaker service. Select Notebook Instances and create a Jupyter Notebook instance as shown below:

How to Use AWS Sagemaker - Notebook Instance

2. On the next page, keep the most default settings as shown. You will need to create an IAM role for S3 bucket creation. If you don’t have that role, you can create it while selecting the role.

3.  Once you select the role, click “create a notebook instance” and it will create a notebook instance. It will take few minutes before it will show it is running. Once the notebook instance is running, click “open” and it will open Jupyter notebook in another tab.

4. Select notebook environment as conda_python3 or anything that you want to use.

Once you have the notebook opened, you can use python or the language of your choice to build a model. For the model, you can easily fetch data from S3 or relational databases from AWS service.

I will not be showing that part in this post. But if you want to refer to a good example, you can visit this post here.

Conclusion

In this post, I showed how one can use AWS Sagemaker to build and train the model for machine learning.

You can subscribe to my blog here.

How to Use API Gateway with Spring Cloud

In this post, I will show how we can use the API Gateway pattern with Spring Cloud. With microservice architecture becoming more and more useful, it has become equally complex how to handle calls to the microservices.

The purpose of microservices is to decouple the application into loosely coupled microservices that can interact with clients and with each other easily.

Importantly, the ease of development and deployment make microservices easier to design based on specific needs.

API Gateway Design Pattern

When the enterprise architecture scales, it becomes complicated with the number of microservices. Clients can directly call these microservices, but there are a few challenges

  1. Each client has to make a request to the exposed microservice API. In many cases, it might have to make multiple server round trips. As a result of this, it increases network latency.
  2. Every microservice has to implement common functionalities like authentication, logging, and authorization.
  3. It becomes harder to change microservice without affecting the client. In reality, the client doesn’t need to know microservice and its implementation behind.

To address these issues, the architecture now contains another layer between the client and the microservices. This is API Gateway.

API Gateway acts like a proxy that routes the request to the appropriate microservices and returns a response to the client. Microservices can also interact with each other through this Gateway.

Usage of API Gateway

There are a few functionalities that API Gateway provides.

Routing

The major usage of API Gateway is routing the request from the client to the appropriate server or microservice. Particularly, API Gateway hides the implementation of API from the client.

Common Functionalities

API Gateway can also implement extra common functionalities and in-process reducing the load from microservices.  These common functionalities include logging, authentication, authorization, load balancing, response caching, retry policies, circuit breakers, rate limiter.

Different API Gateways

There are a number of API Gateways available and one can use any of these based on the needs.

  • Netflix API Gateway (Zuul)
  • Amazon API Gateway
  • Mulesoft
  • Kong API Gateway
  • Azure API Gateway

Overall, which API Gateway to use will depend on your use case. But the most of these gateways provide options to scale, flexibility and support.

In this demo, I will be showing how to use spring-cloud-starter-netflix-zuul library for Netflix API Gateway.

Example of API Gateway with Spring Cloud

However, we will develop two microservices. We will also build an API Gateway using Spring Cloud. This API Gateway will act as a reverse proxy to route to either of the microservices.

So let’s create the first microservice. This will contain a CustomerController like below:


@RestController
@RequestMapping("/customer")
public class CustomerController
{
    @GetMapping("/total")
    public List customers()
    {
        List list = new ArrayList<>();
        list.add("Microsoft");
        list.add("Amazon");
        list.add("Apple");
        return list;
    }
}

This microservice will be running on port 8081. server.port=8081.

Now, let’s create another microservice. This will contain VendorController like below:


@RestController
@RequestMapping("/vendor")
public class VendorController
{
    @GetMapping("/total")
    public List vendors()
    {
        List list = new ArrayList<>();
        list.add("CJI Consultants");
        list.add("Signature Consultants");
        list.add("Deloitte");
        return list;
    }
}

This microservice will be running on port 8082. server.port=8082

API Gateway with Spring Cloud

After all, we will create an API Gateway using Spring Cloud. We need to include the following dependencies:

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
	implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'io.projectreactor:reactor-test'
}

Note the dependency of spring-cloud-starter-gateway. Nevertheless, we will need a RouteLocator type bean to route our requests. This is where we add the configuration in our Api Gateway.

package com.betterjavacode.apigatewaydemo.config;


import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringCloudConfig
{
    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder routeLocatorBuilder)
    {
        return routeLocatorBuilder.routes()
                .route("customerModule", rt -> rt.path("/customer/**")
                        .uri("http://localhost:8081/"))
                .route("vendorModule", rt -> rt.path("/vendor/**")
                        .uri("http://localhost:8082/"))
                .build();

    }
}

As shown above, this configuration bean builds a RouteLocator to route requests related to two modules. Also, note that our gateway service is running at port 8080. If a request is initiated with a gateway address, the API gateway will route it to the appropriate service.

Demo

Let’s start out our microservices and API Gateway service. Two microservices are running on ports 8081 and 8082. The API gateway service is running on port 8080.

Now if I access http://localhost:8080/vendor/total, I will get the list of vendors as follows:

If I access http://localhost:8080/customer/total, I will get the list of customers as follows:

 

Conclusion

Conclusively, I showed how to use API Gateway with Spring Cloud. API Gateway is an important design concept. With an increasing number of microservices, it becomes important to have a common pattern that can handle a lot of the common workload of these services, and API Gateway helps with that.

My book Simplifying Spring Security is currently on discount sale if you are interested.

Example of Spring Boot Application Authentication with AWS Cognito

In this post, I plan to show an example of Spring Boot Application authentication with AWS Cognito. I will show two flows –

  1. OIDC Authentication
  2. SAML Authentication

AWS Cognito

AWS Cognito is a web service from AWS. Cognito is a user directory as well as an authentication mechanism service. In the enterprise industry, every application has two requirements from a user perspective.

  1. User Directory and Synchronization
  2. User Authentication

Cognito makes this easier by allowing the creation of a user pool or an identity pool. Once you have a pool, you can configure an application with the various settings for authentication. Another major advantage of Cognito is that it offers industry-standard security authentication protocols like OAuth 2.0, OpenID Connect, SAML.

There are equally other features like user access handling, but we won’t go into detail about that as part of this post. In this post, I will further show how we can create a user pool and configure an application to use that user pool.

Spring Boot Application

We will use a simple Spring Boot Application. We will integrate this application with AWS Cognito for authentication.

Spring Boot Setup

Add the following dependencies in the Gradle file


	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.security:spring-security-oauth2-client'
	implementation 'org.springframework.security:spring-security-oauth2-jose'

And the application.properties will need oauth2 configuration properties:


# ====================================================================================
## SSL Keystore for HTTPS
# ====================================================================================
#security.require-ssl=true
server.port=8743
server.ssl.key-store-password=******
server.ssl.key-store=classpath:sssstore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=selfsigned_localhost
# =====================================================================================
spring.security.oauth2.client.registration.cognito.clientId=7mtivo8jobdtd6dvocldkmu6qk
spring.security.oauth2.client.registration.cognito.clientSecret=*********
spring.security.oauth2.client.registration.cognito.scope=openid
spring.security.oauth2.client.registration.cognito.redirect-uri=https://localhost:8743/login/oauth2/code/cognito
spring.security.oauth2.client.registration.cognito.clientName=SpringBootCognitoDemo
spring.security.oauth2.client.registration.cognito.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.cognito.issueUri=https://cognito-idp.{awszone}.amazonaws.com/{userPoolId}
spring.security.oauth2.client.provider.cognito.authorization-uri=https://{customDomain}.auth.{awszone}.amazoncognito.com/oauth2/authorize
spring.security.oauth2.client.provider.cognito.token-uri=https://{customDomain}.auth.{awszone}.amazoncognito.com/oauth2/token
spring.security.oauth2.client.provider.cognito.jwk-set-uri=https://cognito-idp.{awszone}.amazonaws.com/{userPoolId}/.well-known/jwks.json
spring.security.oauth2.client.provider.cognito.user-name-attribute= cognito:username

Security Configuration

Our Main Controller class will look like below:



@Controller
public class MainController
{
    @GetMapping("/")
    public String home(Model model, Principal principal)
    {
        model.addAttribute("username", principal.getName());
        return "index";
    }
}

So when we will access our application, it will redirect to AWS Cognito UI for authentication. Based on the selected flow OIDC or SAML, the authentication will happen.

SecurityConfig will be as below:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    public CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity.csrf()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(customLogoutSuccessHandler)
                ;

    }
}

Our simple index template will look like below:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">
    <title>Cognito Demo</title>
</head>
<body>
<div class="container">
    <h1 class="title">OAuth 2.0 Spring Security Cognito Demo</h1>
    <div>
        <div class="box">
            Hello, <strong th:text="${username}"></strong>!
        </div>
        <a th:href="@{/logout}" class="btn btn-primary">Logout</a>
    </div>
</div>
</body>
</html>

 

Create a User Pool

Once you log in to AWS Console, select Cognito as AWS Service. The first screen will show you two options – Create a user pool and Create an identity pool.

We will select Create a user pool.

Enter a suitable name for your user pool and select Step through settings. This will allow us to modify any settings that we don’t want to use for our use case.

Now select user pool attributes for users. These attributes allow us to identify the user and what a user can enter during authentication.

For our demo, we have selected Email address, family name, and given name as attributes. So the user will log in to the application using an email address.

For password policy, we have set that password should be of minimum length 8, should be alphanumeric with upper case and lower case characters.

Most of the other settings we will choose as default. For App Clients Step, we will choose username and password-based authentication for Auth Flow.

Application Client Configuration

Once we create a user pool, we will continue to step through the next set of configurations and that includes the Application Client. In this configuration, we configure our application that will be using the user pool.

We need to enable identity provider and Cognito User Pool.

Configuring Federation Identity Provider

As mentioned previously, we will also configure a SAML Identity Provider for authentication. In this case, the flow will be application -> AWS Cognito -> SAML Identity Provider.

Configure Application in Okta

For this demo, we will use Okta as SAML Identity Provider. Log in to Okta Administrator console , configure a new application for SAML Integration as below:

On the next step, provide Single Sign On URL and this will be https://{yourcustomdomainfromcognito}.auth.{awszone}.amazoncognito.com. We also need to provide Audience URI (SP Entity ID). In this case, Cognito will act as Service Provider to Okta.

We also need to configure Attribute Statements as shown above. Those are the attributes we have configured in our Cognito User Attributes – email, given_name, family_name.

Basically, make sure that the user you plan to use is in Okta Users Directory. It will be the same user that we will need to be either configured in Cognito User Pool or created through sign-up.

Configure Federated Identity Provider in Cognito

  • In our user pool configuration, select Federated Identity Provider.
  • Choose the option of SAML and Cognito will show you to upload a metadata file or metadata URL for Identity Provider. In our case, it will be https://dev-19753289.okta.com/app/exkmt322q1Kl15Rsk5d6/sso/saml/metadata.
  • Type a name for Federated Identity Provider and Save the changes.

Therefore, if we go back to App Client Settings, we should see an option to select Federated Identity Provider. With the new configuration, our settings will look like below:

Anyhow, make sure the user attributes(given_name, family_name, and email) in Cognito are editable.

However, we have completed our configuration. Now we can show the demo.

Demo of Spring Boot Application Authentication with AWS Cognito

Now if I run my application and access it at https://localhost:8743/, I will see the following screen to select an identity provider to authenticate with:

Nevertheless, we can use Okta or Cognito User Pool to log in.

SAML Flow

  1. User accesses the application that sends Authorization Code Flow OAuth request to Cognito
  2. Cognito sends SAML Request to Okta.
  3. The user enters credentials on the okta login screen.
  4. Okta sends the SAML response back to Cognito at endpoint https://{customdomain}.auth.{awszone}.amazoncognito.com/saml2/idpresponse
  5. Cognito processes the saml response and generates auth code and response back to the application.
  6. The application uses auth code to get token from Cognito and authenticates the user on verification of token.

Above all, after successful authentication, the user will see

Instead of Okta, we had chosen Cognito login on the same Home Realm Discovery screen, we will see the following after successful authentication:

Conclusion

In this post, I showed how we can use AWS Cognito for authentication with Spring Boot application. Moreover, Cognito helps in configuring users as well as removing boilerplate code of security flows.

In conclusion, if you have not bought my ebook Simplifying Spring Security, it is available here.

Everything you need to know about Spring Data JPA

In this post, I cover everything you need to know about Spring Data JPA so you can use this library more confidently in your Spring Boot application. I have seen there are a lot of confusion about when to use CrudRepository or JpaRepository, so I will cover that as well.

What is Spring Data JPA?

As the official documentation from Spring says “Spring Data JPA makes it easy to implement JPA based repositories. It improves and eases the implementation of the JPA-based data access layer. Overall, data access applications are easier to implement.”

With Spring Data JPA, one can avoid a lot of boilerplate code for Java Persistent API (JPA) in the application.  The library also makes it easy to query the data from the database taking away a lot of implementation for SQL queries from a developer.

As a developer, you only write repository interfaces including any custom methods and Spring Data JPA will provide the most implementation automatically.

When to use Spring Data JPA?

When to use Spring Data JPA actually depends on your use case. But most Spring applications will need data objects. And if you have data objects, you will need a way to access them, and use them for transactions.

If you are building a database-based web application, then you will end up creating objects that represent your database entities. When one layer accesses these objects or creates the data to store in the database, you will need access APIs. You can implement repository interfaces and it will provide the basic CRUD operations. A programmer doesn’t even have to implement any of these interface methods.

You can also add a custom method in these repository interfaces.

  1. Create a new method in the interface
  2. Write the JPQL query with an annotation @Query at the beginning of the query.
  3. One can also write JPA Named queries

Some other major features of Spring Data JPA are:

  1. Auditing of domain classes
  2. Useful in batch loading, dynamical queries
  3. Support for XML Mapping of entities

Details of the Spring Data JPA library

In your application, you can include the dependency easily as follows:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

Now to enable your JPA repositories, add the following annotation to your main application class:

@EnableJpaRepositories(basePackages = "com.betterjavacode.modelmapperdemo")

Now to add a repository for any of your domain objects, you can create an interface extending JpaRepository or CrudRepository.

This will look like below:


@Repository
public interface OrderRepository extends JpaRepository<Order, Long>
{
    List findAllByCustomer (Customer customer);
}

As you can see above, we have added a new interface that represents a domain object of Order. I have also added a custom method findAllByCustomer. JPA Named Queries will take care of the implementation of this method to fetch all the orders by a customer.

Configuration

Spring Boot configures Hibernate as the default JPA provider. If you want to customize this configuration or change the default JPA provider, you will have to create a entityManagerFactory bean in your Spring Configuration.


@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.betterjavacode.modelmapperdemo")
@PropertySource("classpath:application.properties")
public class DataConfig {

	private final String PROPERTY_DRIVER = "driver";
	private final String PROPERTY_URL = "url";
	private final String PROPERTY_USERNAME = "user";
	private final String PROPERTY_PASSWORD = "password";
	private final String PROPERTY_SHOW_SQL = "hibernate.show_sql";
	private final String PROPERTY_DIALECT = "hibernate.dialect";

	@Autowired
	Environment environment;

	@Bean
	LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean lfb = new LocalContainerEntityManagerFactoryBean();
		lfb.setDataSource(dataSource());
		lfb.setPersistenceProviderClass(HibernatePersistence.class);
		lfb.setPackagesToScan("com.betterjavacode.modelmapperdemo");
		lfb.setJpaProperties(hibernateProps());
		return lfb;
	}

	@Bean
	DataSource dataSource() {
		DriverManagerDataSource ds = new DriverManagerDataSource();
		ds.setUrl(environment.getProperty(PROPERTY_URL));
		ds.setUsername(environment.getProperty(PROPERTY_USERNAME));
		ds.setPassword(environment.getProperty(PROPERTY_PASSWORD));
		ds.setDriverClassName(environment.getProperty(PROPERTY_DRIVER));
		return ds;
	}

	Properties hibernateProps() {
		Properties properties = new Properties();
		properties.setProperty(PROPERTY_DIALECT, environment.getProperty(PROPERTY_DIALECT));
		properties.setProperty(PROPERTY_SHOW_SQL, environment.getProperty(PROPERTY_SHOW_SQL));
		return properties;
	}

	@Bean
	JpaTransactionManager transactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
		return transactionManager;
	}
}

The above code shows how to add configuration if you plan to customize the default hibernation configuration.

  • @EnableTransactionManagement – enables the transactions in the application, especially when creating or updating the data.
  • @PropertySource – reads the application properties that will be used in Datasource bean.

As mentioned previously, Spring Boot uses Hibernate as the default persistence provider. But there are few other persistence providers available like OpenJPA or EclipseLink.

Difference between CrudRepository and JpaRepository

We previously discussed about creating repository interfaces that extend JpaRepository or CrudRepository. But when do you use which one? In this section, I clarify more details about these repository interfaces.

CrudRepository is the base interface. JpaRepository extends PagingAndSortingRepository which in turn extends CrudRepository.

  • CrudRepository – This provides CRUD functions.
  • PagingAndSortingRepository – This provides functions for sortable and pageable data.
  • JpaRepository – JpaRepository provides JPA-related functions to flush the persistence context as well as delete data in a batch.

In short, one can use JpaRepository as it will include all the other methods from CrudRepository and PagingAndSortingRepository.

Conclusion

In this post, I showed the details of Spring Data JPA library and how to use it. I also showed configuration details and what repository interface to use.

 

Conversion of Entity to DTO Using ModelMapper

In this post, I will show how we can achieve the conversion of entity to DTO using the ModelMapper library.  We will basically create a simple REST API for orders while showing the transformation of Entity to DTO and vice versa.

Understanding Enterprise Architecture

In most enterprise architecture, you will have REST APIs. A consumer of these APIs sends a request and the server responds with a response. The transformation of request to response happens behind the API. You perform business logic and modify these objects.

Traditionally, there are three layers in the architecture. Web layer, business layer, and database layer.

So, your object in the database layer will be completely different from the same object in the web layer. Database entities from the database layer contain certain fields that you don’t need in the web layer. More so, any object from the web layer should be user-friendly. Users don’t have to guess what they are dealing with. It should be self-explanatory. This will be more clear when I show the implementation of this.

 

Separation of Layers between Entity and DTO

Data Transfer Objects (DTO) are the objects that move from one layer to another. These objects are more user-friendly and contain only the most required fields.

On the other hand, database entities represent database tables. A lot of auto-generated fields can be unnecessary for users to know about. Nevertheless, they are part of database entities. In DTO, we ignore these fields. Since these fields are auto-generated, our database layer code can handle that.

But when the object travels from the web layer to the database layer, it needs to be transformed for that layer to use. In the next section, I will show how we can achieve this conversion from entity to DTO using the ModelMapper library.

The Entity to DTO Using ModelMapper

ModelMapper library provides an easier way to convert an entity object to DTO and vice versa.

In this demo, I have a scenario where a customer orders an item. An order for the item gets created. We save order details, customer details, and the address of the customer.

To able to use this library in our application, add the dependency as follows:

implementation 'org.modelmapper:modelmapper:2.3.0'

Also if we want to use ModelMapper library functions, we will add a bean for the same as follows:

        @Bean
	public ModelMapper modelMapper()
	{
		return new ModelMapper();
	}

Previously, I stated that a customer will be able to order. So, we will implement this by having a REST API that will create Order details, Customer details.

Domain Layer

In this architecture, we have orders that customers order at certain addresses.

In a database entity diagram, it will look like below:

A customer can order multiple items, so multiple orders. Multiple orders can go to a single address.

Our domain objects will look like below, starting with Order:


package com.betterjavacode.modelmapperdemo.models;

import javax.persistence.*;
import java.io.Serializable;

@Entity(name = "Order")
@Table(name = "orders")
public class Order implements Serializable
{
    private static final long serialVersionUID = 7385741327704693623L;

    public Order()
    {

    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private long id;

    @Column(name ="order_item")
    private String orderItem;

    @Column(name = "description")
    private String description;


    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;


    @ManyToOne
    @JoinColumn(name = "address_id")
    private Address address;
    
    // Getters and setters omitted for demo purposes


}

The address will be:


package com.betterjavacode.modelmapperdemo.models;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Entity(name = "Address")
@Table(name = "address")
public class Address implements Serializable
{
    private static final long serialVersionUID = -439961851267007148L;

    public Address()
    {

    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private long id;

    @Column
    private String street;

    @Column
    private String city;

    @Column
    private String state;

    @Column
    private String country;

    @Column
    private int zipcode;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List orderList = new ArrayList<>();


}

And Customer will be:


package com.betterjavacode.modelmapperdemo.models;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Entity(name = "Customer")
@Table(name = "customer")
public class Customer implements Serializable
{
    private static final long serialVersionUID = -2205735699915701334L;

    public Customer()
    {

    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column
    private String email;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List orderList = new ArrayList<>();


}

These three objects order, customer, and address represent our database entities and part of the database layer. The rest of the persistence is straightforward with repositories.

Web Layer

Web layer mostly focuses on the controllers that we create for our APIs. These controllers are responsible for receiving the request from the client. Also, the objects that we will expose through APIs will be DTO object. This DTO Object for Order will look like below:


package com.betterjavacode.modelmapperdemo.dtos;

public class OrderDTO
{
    String orderItem;
    String orderDescription;
    String customerFirstName;
    String customerLastName;
    String customerEmail;
    String streetAddress;
    String cityAddress;
    String stateAddress;
    String countryAddress;
    int zipcodeAddress;

   // Getters and Setters omitted for demo

}

This DTO object includes fields from Order, Customer, and Address. Our API will receive this object in POST request, we will transform that DTO object to an entity object using ModelMapper library and then pass that entity object to our Service class to process further.

OrderController will be as follows:


package com.betterjavacode.modelmapperdemo.controllers;

import com.betterjavacode.modelmapperdemo.dtos.OrderDTO;
import com.betterjavacode.modelmapperdemo.models.Order;
import com.betterjavacode.modelmapperdemo.service.IOrderService;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/v1/betterjavacode/orders")
public class OrderController
{
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);

    @Autowired
    private IOrderService orderService;

    @Autowired
    private ModelMapper modelMapper;

    @PostMapping
    public OrderDTO createOrder(@RequestBody OrderDTO orderDTO)
    {
        Order order = convertToEntity(orderDTO);
        Order orderCreated = orderService.createOrder(order);

        return convertToDTO(orderCreated);
    }

    @GetMapping("/{customerId}")
    public List getAllOrders(@PathVariable("customerId") long customerId)
    {
        List orderList = orderService.getAllOrdersForCustomer(customerId);
        List orderDTOs = new ArrayList<>();
        for(Order order : orderList)
        {
            orderDTOs.add(convertToDTO(order));
        }
        return orderDTOs;
    }


    private Order convertToEntity (OrderDTO orderDTO)
    {
        LOGGER.info("DTO Object = {} ", orderDTO);

        Order order = modelMapper.map(orderDTO, Order.class);

        return order;
    }

    private OrderDTO convertToDTO (Order order)
    {
        OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
        return orderDTO;
    }
}

We have a POST API to create orders and a GET API to retrieve orders for a customer.

ModelMapper Library

In our controller, we are using ModelMapper bean to convert DTO object to entity and entity object to DTO.

How does the ModelMapper library actually achieve this?

When a mapper calls the map method, it analyzes the source and destination types to determine which properties to match. It uses a matching strategy and configuration to map these properties. Once, the properties are mapped, it will map the data.

So if we look at our DTO class, we have properties like customerFirstName, customerLastName that match to Customer Entity object, while properties like streetAddress, cityAddress will match to properties from Address object.

ModelMapper also offers a way to explicitly map the properties if you choose to do that.


modelMapper.typeMap(Order.class, OrderDTO.class).addMappings(mapper -> {
  mapper.map(src -> src.getBillingAddress().getStreet(),
      Destination::setBillingStreet);
  mapper.map(src -> src.getBillingAddress().getCity(),
      Destination::setBillingCity);
});

The library offers three types of matching strategies:

  1. Standard – In this strategy, the library matches the source properties to destination properties intelligently. This strategy is configured by default. All destination property name tokens must match.
  2. Loose – Properties of source and destination are matched loosely. If the property hierarchies of source and destination objects are dissimilar, then the loose strategy can work. The last destination property name must have all tokens matched.
  3. Strict – Source properties should strictly match destination properties. Tokens match in a strict order. This strategy allows no ambiguity.

A Complete Demo of Entity to DTO using ModelMapper

We have shown our REST Controller and Domain objects. Now, I will show how we can using postman to call this REST API by passing a DTO object to POST API.

We will create an order of an item that a customer orders.

In the request, I passed a DTO object that contains information for order, customer, and address.

In our service layer, we process converted entity objects, validate business rules and save this information to create the order.

Avoiding Technical Debt

Understanding the concept of DTO and Entity objects is important. When to use what kind of object can help you avoid technical debt. From personal experience, I have seen a lot of junior developers make the mistake of using entity objects in a web layer. Depending on your application, this can increase the complexity of the system.

Conclusion

In this post, I showed how we can convert entity to DTO using modelmapper library.  You can download the modelmapper library here. The code for this demo is available in my GitLab repository. If you enjoyed this post, consider subscribing to my blog here.

References

  1. Model Mapper Library – ModelMapper