Microservices with Spring

Purpose of this article is to provide examples and to demonstrate building a microservice applications using common patterns with Spring Boot and Spring Cloud Netflix OSS(Zuul, Eureka, and Feign), Hibernate, and JJWT

The Project has been taken from one of my previous projects I’ve built as a Monolith. I will not be including the whole application. Only some components of it.

Source code can be found here

Architecture

 

1-QnEUoCAR7T-7VFaguGFj-w

 

Notes:

All services will have their own database( Identity management service, Ticketing Service, and Customer Service)

Building Our Services — Functional Services

Functional Services are services that provides the core business logic of our application.

Identity Management Service (authentication-service)

The Identity management service that handles token issuance, persisting user information such as roles, username, password, and etc. We can roll up our own or use an existing identity management API like Auth0.

We start off by placing an annotation on our Main class

@SpringBootApplication
@EnableEurekaClient
public class FriflowAdminApplication {
   public static void main(String[] args) {
      SpringApplication.run(FriflowAdminApplication.class, args);
   }
}

The annotation  @EurekaClient specifies that our application is a subscriber of an Existing Eureka Server. Where it will automatically register itself as a service.

We can configure it’s settings with our application.yml

eureka:
  client:
    serviceUrl:
      defaultZone: ${vcap.services.eureka-service.credentials.uri:http://127.0.0.1:8761}/eureka/

Side Note:

As long as Spring Cloud Netflix and Eureka Core are on the classpath any Spring Boot application with @EnableEurekaClient will try to contact a Eureka

The eureka.client.serviceUrl.defaultZone is the address of our Service Registry where our EurkeClient, which is the Identity Management Service will automatically register itself.

To name our service we specify it on our bootstrap.yml

spring:
  application:
    name: authentication-service

Core Business Logic

As the core logic of this service lies in this package

1-Qw2umexvL2rYkMoQOJ20RA

Once we’ve validated the user who’s requesting an to our api. We would then issue an Authentication token using JWT which can be found inside the JwtTokenIssuerService

@Override
public String issueToken(String userName) {

    final long nowMillis = System.currentTimeMillis();
    final long expMillis = nowMillis + (ONE_MINUTE_IN_MILLIS * TOKEN_DURATION_IN_MIN);

    byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);
    Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

    return Jwts
            .builder()
            .setIssuedAt(new Date(nowMillis))
            .setExpiration(new Date(expMillis))
            .setSubject(userName)
            .setIssuer(issuer)
            .signWith(signatureAlgorithm, signingKey).compact();

}

You can know more about JWT here, and JJWT here.

And then we have a base REST Controllers that performs our CRUD and some little information processing within these given packages

1-_ATnYL8n6XMaa7f-Pctc3A

Workflow Management Service (ticketing-service)

We can think of the ticketing service as a Ticketing System or a small workflow management system that issues various types tickets such as quotation tickets which contains product inquiries, pricing, materials used. This where most or business processing occurs/execute.

We start off again with our basic configuration

@SpringBootApplication
@EnableJpaRepositories(basePackages = {"org.brightworks.friflow.repo"})
@EntityScan(basePackages =
        {"org.brightworks.friflow.domain",
         "org.brightworks.friflow.domain.process"
        })
@ConditionalOnClass({SpringSecurityDialect.class})
@EnableEurekaClient
@EnableDiscoveryClient
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).run();
    }
}

Ticketing Service/Workflow Management Service Domain ERD

1-jQS-oewiCjxbPgwADatmAw

Some Our API Endpoints/Controllers are specified under this package

1-aURVKJDEGpGMBV_6LFz5Xw

Building our Services — Infrastructure Services

There are several patterns in distributed systems that can aid us in making our Functional/Core services work together. Spring cloud provides those tools to implement some of those Patterns.

Service Registry and Service Discovery

We will be using Eureka as our Service registry, Where all of our services will be self-registered. Another way to think about Service Registry is a phone-book of our existing services.

It’s now easier to set up our Service Discovery Code thanks to Spring Cloud Eureka.

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
   public static void main(String[] args) {
      SpringApplication.run(EurekaServerApplication.class, args);
   }
}

We configure it as follows.

server:
  port: ${PORT:8761}

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0

We’re simply saying we will register this Eureka Server at Port 8761. and eureka.client.registerWithEureka means Eureka Server is not going to register itself as a client.

All of our services will be self-registered. No need to add more configurations to what we did above. Upon running you’ll see all of the services registered with Eureka.

 

1-PEyvuEfs62WnNg4LCCSzkQ

Building our Services — Infrastructure Services. Putting it all together

Edge Service/Api Gateway

Edge service will act as the main entry point of clients. It’s primary purpose is to aggregate results from different services, act as a proxy, and perform authorization using JJWT. We will be using Feign, Zuul, and Ribbon for this purpose.

It is suggested that we implement oauth2 with JJWT but For simplicity, we will be only using JJWT and use a filter component to implement authorization.

We start first by specifying our Application config

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableZuulProxy
public class FriflowApiGatewayApplication {

   @Value("${jwt.security.key}")
   private String jwtKey;

   public static void main(String[] args) {
      SpringApplication.run(
FriflowApiGatewayApplication.class, args);
   }

   @Bean
   public FilterRegistrationBean filterApiBean() {
      FilterRegistrationBean registrationBean = new   FilterRegistrationBean();
      ApiAccessFilter securityFilter = new ApiAccessFilter(jwtKey);
      registrationBean.setFilter(securityFilter);
      registrationBean.addUrlPatterns("/api/*");
      return registrationBean;
   }
}

@EnableFeignClients — Annotation responsible for scanning interface if they are annotated with @FeignClient

@EnableDiscoveryClient — Annotation responsible for activating whichever Discovery client available in our classpath(In this case, Netflix Eureka Discovery Client)

@EnableZuulProxy — Will turn the this application as a reverse proxy that forwards requests to other services.

The filterApiBean is the bean we use to perform filtering of unauthorize requests. It basically checks if it has a JJWT token, and if it’s still valid.

Forwarding the requests to appropriate services — Identity Management Api

Our Api Gateway will now be the main entry point of our clients(e.g mobile devices. another webapp and etc)

In order for us to to forward requests to ticketing-service. We would need first to retrieve an access token from our identity management service.

zuul.routes.authentication.path=/authentication-service/**
zuul.routes.authentication.serviceId=authentication-service
ribbon.eureka.enabled=true

In the configuration above we will be proxying all requests that’s coming from /authentication-service/ to authentication-service. Notice how we did not specify the url our authentication-serivce(identity management api) Thanks to eureka and ribbon it will automatically forward the requests to the existing/available server

To retrieve an access token we can send a posts request to http://localhost:8082/authentication-service/login

 

1-nN7Vo0LHmsPAnXl8N0qUng

once we have entered a valid username and password. We will receive a token.

Forwarding the requests to appropriate services — Ticket Management API

For this example. Although we can use the Zuul proxy as we did above. We will use Feign client. This can be useful if we want to aggregate results/get results from different services.

@FeignClient("ticketing-service")
public interface QuotationClient {

    @RequestMapping(method = RequestMethod.GET,value = "/quotations/dummy")
    QuotationDTO getDummy();

    @RequestMapping(method = RequestMethod.GET,value = "/quotations/{ticketNo}")
    QuotationDTO getByTicket(@PathVariable("ticketNo") String ticketNo);

    @RequestMapping(method = RequestMethod.POST,value = "/quotations")
    QuotationDTO save(@RequestBody QuotationDTO quotation);

    @RequestMapping(method = RequestMethod.PUT,value = "/quotations")
    QuotationDTO update(@RequestBody QuotationDTO quotation);
}

 

The instructions for running each individual service, and code is available at Github

There’s still a lot to improve on this sample project (e.g security, and oauth2, and etc but hopefully this article provided you a ground up on migrating/building your applications using the Microservice design with spring boot.

Side Note: The code was taken from one of my old project that’s been built as monolithic. Some coding conventions/approach might be outdated, and had incurred technical debt. and I will try to update and clean up the code as soon as I can.

Improvements and suggestions are welcome 🙂

references and further readings:

http://microservices.io/
https://player.oreilly.com/videos/9781491944615
http://www.oreilly.com/programming/free/microservices-vs-service-oriented-architecture.csp http://shop.oreilly.com/product/0636920033158.do
https://herbertograca.com/2017/01/26/microservices-architecture/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s