Microservices Reference Architecture - Springboot

kjana83

Janarthanan K

Posted on September 28, 2021

Microservices Reference Architecture - Springboot

We will build a reference Microservices architecture with just having two services in this blog. Its very basic minimal setup based on Conference management.

In this we will create two microservices, Cloud Gateway to communicate to both the services, Service Registry, Hystrix Dashboard, Config Server and Seluth login to view logs in Zipkin.

1. Speaker Service

Let's create Speaker Service. Add following dependencies.
Sprint Initializer for Speaker
And hit Generate.
Lets create Session Entity, Controller, Service and Repository.

Speaker Entity

package com.jk.speaker.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Speaker {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long speakerId;
    private String name;
    private String role;
    private String description;
}
Enter fullscreen mode Exit fullscreen mode

Speaker Service

package com.jk.speaker.service;

import com.jk.speaker.entity.Speaker;
import com.jk.speaker.repository.SpeakerRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class SpeakerService {

    @Autowired
    private SpeakerRepository speakerRepository;

    public Speaker saveSpeaker(Speaker speaker) {
      log.info("Speaker saved - Service");
      return speakerRepository.save(speaker);
    }

    public Speaker getBySepeakerId(Long id) {
        log.info("Speaker by Id - Service");
        return speakerRepository.getBySpeakerId(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

Speaker Repository to store it in H2.

package com.jk.speaker.repository;

import com.jk.speaker.entity.Speaker;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SpeakerRepository extends JpaRepository<Speaker, Long> {
    Speaker getBySpeakerId(Long id);
}
Enter fullscreen mode Exit fullscreen mode

Create application.yml with port 9092.

server:
  port: 9092
Enter fullscreen mode Exit fullscreen mode

Start the service.! First Microservice is ready. Let's test with Postman.
Test Session service in postman
Hurray! Working.

2. Session Service

Now shall create Session Service, similar to Speaker. For this service will use port 9091.

We shall Start invoke Speaker service from Session service.
To do this. Also add Speakerid in the Session entity and create Value Objects to return both speaker and session.

package com.jk.sessions.valueObject;

import com.jk.sessions.entity.Session;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseTemplateValueObject {

    private Session session;
    private Speaker speaker;
}
Enter fullscreen mode Exit fullscreen mode

Also create RestTemplate bean. to invoke Speaker service

package com.jk.sessions;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class Application {

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

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
Enter fullscreen mode Exit fullscreen mode

After that invoke from the Service and return the VO.

package com.jk.sessions.service;

import com.jk.sessions.entity.Session;
import com.jk.sessions.repository.SessionRepository;
import com.jk.sessions.valueObject.ResponseTemplateValueObject;
import com.jk.sessions.valueObject.Speaker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Service
public class SessionService {

    @Autowired
    private SessionRepository sessionRepository;

    @Autowired
    private RestTemplate restTemplate;

    public Session saveSession(Session session) {
        log.info("Save Session - Service");
        return sessionRepository.save(session);
    }

    public Session getSessionById(Long id) {
        log.info("Session by Id - Service");
        return sessionRepository.getBySessionId(id);
    }

    public ResponseTemplateValueObject getSessionWithSpeakerById(Long id) {
        ResponseTemplateValueObject vo= new ResponseTemplateValueObject();

        Session session = sessionRepository.getBySessionId(id);

        Speaker speaker = restTemplate.getForObject("http://localhost:9092/speakers/" +
                session.getSessionId(), Speaker.class);

        vo.setSession(session);
        vo.setSpeaker(speaker);
        return vo;
    }
}
Enter fullscreen mode Exit fullscreen mode

Test the same in Postman, now you can see both are VO contains both Speaker and Session details

Test in Postman

3. Service Registry

You have noticed that we have hard coded the url. When we have multiple service, managing will be hard. So, will create service registry to manage it with help of Eureka Server
Initialize with Eureka Server
Add EnableEurekaServer decorator

And mark this as Eureka client false to notify this as server in application.yml and run this in the port 8761.

server:
  port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
Enter fullscreen mode Exit fullscreen mode

Go to browser try for http://localhost:8761/. No services are attached yet. You should see empty instances.

Lets add the EurekaClient dependency in both Author and Session services.
Add following in pom.xml

    <properties>
        <java.version>11</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>

            </dependency>
        </dependencies>
    </dependencyManagement>
Enter fullscreen mode Exit fullscreen mode

Now go to application.yml and Eureka server url and add name for your service.

spring:
  application:
    name: SPEAKER-SERVICE

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:9090/eureka/
    instance:
      hostname: localhost
Enter fullscreen mode Exit fullscreen mode

Also decorate both microservices Application with @EnableEurekaClient.

Both services are up and running
You can see services are up and running. Now go to Session-service and replace rest url from locahost:9092 to 'SPEAKER-SERVICE' and add @Loadbalanced decorator in your Rest template. This will ensure when you have multiple instances load will get distributed.

4. API Gateway

When you have multiple Microservices running, you need to manage the exceptions and routing all the services via one gateway. Its time to create API gateway.

API Gateway

server:
  port: 9090

spring:
  application:
    name: API-GATEWAY
  cloud:
    gateway:
      routes:
        - id: SPEAKER-SERVICE
          uri: lb://SPEAKER-SERVICE
          predicates:
            - Path=/speakers/**
        - id: SESSION-SERVICE
            uri: lb://SESSION-SERVICE
            predicates:
              - Path=/sessions/**

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka/
    instance:
      hostname: localhost
Enter fullscreen mode Exit fullscreen mode

Now start the API Gateway. Check in the registry, you should see 3 services running including API Gateway.
Will test now with Gateway url 9090 for both Speaker and Sessions service.
Postman Test
Postman Test
Great! Both are working via API Gateway.
Will add circuit breaker to notify if the services are down using Hystrix.

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
Enter fullscreen mode Exit fullscreen mode

Add fallback controller to notify if there is any service failures. I have added 5 secs timeout.

spring:
  application:
    name: API-GATEWAY
  cloud:
    gateway:
      routes:
        - id: SPEAKER-SERVICE
          uri: lb://SPEAKER-SERVICE
          predicates:
            - Path=/speakers/**
          filters:
            - name: CircuitBreaker
              args:
                name: SPEAKER-SERVICE
                fallbackuri: forward:/speakerFallback
        - id: SESSION-SERVICE
          uri: lb://SESSION-SERVICE
          predicates:
            - Path=/sessions/**
          filters:
            - name: CircuitBreaker
              args:
                name: SESSION-SERVICE
                fallbackuri: forward:/sessionFallback
Enter fullscreen mode Exit fullscreen mode

Add fallback method in the fallback controller.

package com.jk.api.gateway.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FallbackController {

    @GetMapping("/speakerFallback")
    public String speakerServiceFallback() {
        return "User service is down";
    }

    @GetMapping("/sessionFallback")
    public String sessionServiceFallback() {
        return "Session service is down";
    }
}
Enter fullscreen mode Exit fullscreen mode

Now bring down one service and test the service in gateway. You will notice the above error message.

Now we can setup the Hystrix dashboard to monitor service outages.

Add management endpoint for the dashboard in Gateway.

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream
Enter fullscreen mode Exit fullscreen mode

5. Hystrix Dashboard

Create new Hystrix Dashboard application from Springboot Initializer. Add Hystrix and Eureka client dependency.

Enable @EnableEurekaClient, @EnableHystrixDashboard in the dashboard application.
Most importantly update application.yml with

server:
  port: 9000

spring:
  application:
    name: HYSTRIX-DASBHBOARD

hystrix:
  dashboard:
    proxy-stream-allow-list: "*"
Enter fullscreen mode Exit fullscreen mode

if you get error in the dashboard then proxy-stream-allow-list with 'your ipaddress'

Now run the Hystrix dashboard and gateway application. Test both session and speaker service with positive negative tests. Check the dashboard. You can see various stats.

Hystrix Dashboard


6. Config Server

As you noticed we are repeating some of the configs like Eureka Client. This can be moved to any config location like Github or your local repository.
We can create Config Server application via spring initializer. Add Eureka Client and Config Server dependency.

And add @EnableEurekaClient @EnableConfigServer in the application
Create a github repo and move common props into application.yml file in the github repo and add that reference in teh application.yml of config server application.

server:
  port: 9080

spring:
  application:
    name: CONFIG-SERVER
  cloud:
    config:
      server:
        git:
          uri: https://github.com/kjana83/config-server
          clone-on-start: true
Enter fullscreen mode Exit fullscreen mode

and create bootstrap.yml in all the application except Eureka Service registry
And move Eureka client config to the bootstrap.yml.
Also add the config-client dependency

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
Enter fullscreen mode Exit fullscreen mode

7. Zipkin Server.

Go to https://zipkin.io/pages/quickstart.html and download zipkin jar package run from your terminal java -jar zipkin.jar

And now we can add sleuth logging to generate traceid and span ids. Same can be used for tracing in Zipkin.
Add following dependency in both session and speaker service.

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
Enter fullscreen mode Exit fullscreen mode

Also add the zipkin url reference in the application.yml

spring:
  application:
    name: SESSION-SERVICE
  zipkin:
    base-url: http://localhost:9411/
Enter fullscreen mode Exit fullscreen mode

and test your application in postman. And open localhost:9411 in browser to trace your logs.
Zipkin logs
Now we have the complete microservices ready. We will cover each service in more details in different blog.

Code can be found in Github

microservices

Microservices reference architecure.






đź’– đź’Ş đź™… đźš©
kjana83
Janarthanan K

Posted on September 28, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related