microservices
Microservices reference architecure.
Posted on September 28, 2021
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.
Let's create Speaker Service. Add following dependencies.
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.
Hurray! Working.
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
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
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
.
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.
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.
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.
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
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.
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>
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.
Now we have the complete microservices ready. We will cover each service in more details in different blog.
Code can be found in Github
Posted on September 28, 2021
Sign up to receive the latest update from our blog.
November 29, 2024