Microservices Reference Architecture - Springboot
Tags: microservices
Published: 2021-09-28 02:01:28

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tx3l3i0y5q5aj7xdh15q.png)
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;
}
```
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);
    }
}

```
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);
}
```

Create application.yml with port 9092. 
```
server:
  port: 9092
```
Start the service.! First Microservice is ready. Let's test with Postman. 
![Test Session service in postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jjybvaahfvbrlul59vo.png)
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;
}
```
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();
	}
}
```
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;
    }
}
```
Test the same in Postman, now you can see both are VO contains both Speaker and Session details

![Test in Postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g1mmox0nbeutygembjqb.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5kzcjt2cpf01fp716nh.png)
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
```
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>
```

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
```
Also decorate both microservices Application with `@EnableEurekaClient`.

![Both services are up and running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/490h33xen3trk9tscyc0.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5bp7v2jj7buksgvp52e6.png)

```
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
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zgl88g4t30810lz2573s.png)
![Postman Test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhnne2wt1u2c9qpkwzqb.png)
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>
```

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
```

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";
    }
}
```
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
```
## 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: "*"
```
*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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdmn0l7d9gd82nbljiz3.png)

---
## 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
```

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>
```
## 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>
```
Also add the zipkin url reference in the application.yml
```
spring:
  application:
    name: SESSION-SERVICE
  zipkin:
    base-url: http://localhost:9411/
``` 
and test your application in postman. And open localhost:9411 in browser to trace your logs.
![Zipkin logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzb44o0fzutbv4rjk8u2.png)
Now we have the complete microservices ready. We will cover each service in more details in different blog.

Code can be found in Github
{% github kjana83/microservices %}