Microservices Architecture: Deploy Locally With Java, Spring, and MongoDB
Maxime Beugnet4 min read • Published Aug 29, 2024 • Updated Aug 29, 2024
FULL APPLICATION
Rate this tutorial
"Microservices are awesome and monolithic applications are evil."
If you are reading this article, you have already read that a million times, and I'm not the one who's going to tell
you otherwise!
In this post, we are going to create a microservices architecture using MongoDB.
The source code is available in these two repositories.
1 git clone git@github.com:mongodb-developer/microservices-architecture-mongodb.git 2 git clone git@github.com:mongodb-developer/microservices-architecture-mongodb-config-repo.git
We are going to use Spring Boot and Spring Cloud dependencies to build our architecture.
We need several services to run this project. Let's talk about them one by one.
As you read this post, I suggest that you follow the instructions in the README.md file and start the service related to each section.
The first service that we need is a configuration server.
This service allows us to store all the configuration files of our microservices in a single repository so our
configurations are easy to version and store.
The configuration of our config server is simple and straight to the point:
1 spring.application.name=config-server 2 server.port=8888 3 spring.cloud.config.server.git.uri=${HOME}/Work/microservices-architecture-mongodb-config-repo 4 spring.cloud.config.label=main
It allows us to locate the git repository that stores our microservices configuration and the branch that should be
used.
Note that the only "trick" you need in your Spring Boot project to start a config server is the
@EnableConfigServer
annotation.1 package com.mongodb.configserver; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.config.server.EnableConfigServer; 6 7 8 9 public class ConfigServerApplication { 10 public static void main(String[] args) { 11 SpringApplication.run(ConfigServerApplication.class, args); 12 } 13 }
A service registry is like a phone book for microservices. It keeps track of which microservices are running and where
they are located (IP address and port). Other services can look up this information to find and communicate with the
microservices they need.
A service registry is useful because it enables client-side load balancing and decouples service providers from
consumers without the need for DNS.
Again, you don't need much to be able to start a Spring Boot service registry. The
@EnableEurekaServer
annotation
makes all the magic happen.1 package com.mongodb.serviceregistry; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 7 8 9 public class ServiceRegistryApplication { 10 public static void main(String[] args) { 11 SpringApplication.run(ServiceRegistryApplication.class, args); 12 } 13 }
The configuration is also to the point:
1 spring.application.name=service-registry 2 server.port=8761 3 eureka.client.register-with-eureka=false 4 eureka.client.fetch-registry=false
The last two lines prevent the service registry from registering to itself and retrieving the registry from itself.
The API gateway service allows us to have a single point of entry to access all our microservices. Of course, you should
have more than one in production, but all of them will be able to communicate with all the microservices and distribute
the workload evenly by load-balancing the queries across your pool of microservices.
Also, an API gateway is useful to address cross-cutting concerns like security, monitoring, metrics gathering, and
resiliency.
When our microservices start, they register themselves to the service registry. The API gateway can use this registry to
locate the microservices and distribute the queries according to its routing configuration.
1 server: 2 port: 8080 3 4 spring: 5 application: 6 name: api-gateway 7 cloud: 8 gateway: 9 routes: 10 - id: company-service 11 uri: lb://company-service 12 predicates: 13 - Path=/api/company/**,/api/companies 14 - id: employee-service 15 uri: lb://employee-service 16 predicates: 17 - Path=/api/employee/**,/api/employees 18 19 eureka: 20 client: 21 register-with-eureka: true 22 fetch-registry: true 23 service-url: 24 defaultZone: http://localhost:8761/eureka/ 25 instance: 26 hostname: localhost
Note that our API gateway runs on port 8080.
Finally, we have our MongoDB microservices.
Microservices are supposed to be independent of each other. For this reason, we need two MongoDB instances: one for
each microservice.
Note that in
the configuration files for the
company and employee services, they are respectively running on ports 8081 and 8082.
1 spring.data.mongodb.uri=${MONGODB_URI_1:mongodb://localhost:27017} 2 spring.threads.virtual.enabled=true 3 management.endpoints.web.exposure.include=* 4 management.info.env.enabled=true 5 info.app.name=Company Microservice 6 info.app.java.version=21 7 info.app.type=Spring Boot 8 server.port=8081 9 eureka.client.register-with-eureka=true 10 eureka.client.fetch-registry=true 11 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ 12 eureka.instance.hostname=localhost
1 spring.data.mongodb.uri=${MONGODB_URI_2:mongodb://localhost:27018} 2 spring.threads.virtual.enabled=true 3 management.endpoints.web.exposure.include=* 4 management.info.env.enabled=true 5 info.app.name=Employee Microservice 6 info.app.java.version=21 7 info.app.type=Spring Boot 8 server.port=8082 9 eureka.client.register-with-eureka=true 10 eureka.client.fetch-registry=true 11 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ 12 eureka.instance.hostname=localhost
Note that the two microservices are connected to two different MongoDB clusters to keep their independence. The
company service is using the MongoDB node on port 27017 and the employee service is on port 27018.
Of course, this is only if you are running everything locally. In production, I would recommend to use two clusters on
MongoDB Atlas. You can overwrite the MongoDB URI with the environment variables (see README.md).
At this point, you should have five services running:
- A config-server on port 8888
- A service-registry on port 8761
- An api-gateway on port 8080
- Two microservices:
- company-service on port 8081
- employee-service on port 8082
And two MongoDB nodes on ports 27017 and 27018 or two MongoDB clusters on MongoDB Atlas.
1 DELETE Companies 2 2 3 DELETE Employees 4 2 5 6 POST Company 'MongoDB' 7 POST Company 'Google' 8 9 GET Company 'MongoDB' by 'id' 10 { 11 "id": "661aac7904e1bf066ee8e214", 12 "name": "MongoDB", 13 "headquarters": "New York", 14 "created": "2009-02-11T00:00:00.000+00:00" 15 } 16 17 GET Company 'Google' by 'name' 18 { 19 "id": "661aac7904e1bf066ee8e216", 20 "name": "Google", 21 "headquarters": "Mountain View", 22 "created": "1998-09-04T00:00:00.000+00:00" 23 } 24 25 GET Companies 26 [ 27 { 28 "id": "661aac7904e1bf066ee8e214", 29 "name": "MongoDB", 30 "headquarters": "New York", 31 "created": "2009-02-11T00:00:00.000+00:00" 32 }, 33 { 34 "id": "661aac7904e1bf066ee8e216", 35 "name": "Google", 36 "headquarters": "Mountain View", 37 "created": "1998-09-04T00:00:00.000+00:00" 38 } 39 ] 40 41 POST Employee Maxime 42 POST Employee Tim 43 44 GET Employee 'Maxime' by 'id' 45 { 46 "id": "661aac79cf04401110c03516", 47 "firstName": "Maxime", 48 "lastName": "Beugnet", 49 "company": "Google", 50 "headquarters": "Mountain View", 51 "created": "1998-09-04T00:00:00.000+00:00", 52 "joined": "2018-02-12T00:00:00.000+00:00", 53 "salary": 2468 54 } 55 56 GET Employee 'Tim' by 'id' 57 { 58 "id": "661aac79cf04401110c03518", 59 "firstName": "Tim", 60 "lastName": "Kelly", 61 "company": "MongoDB", 62 "headquarters": "New York", 63 "created": "2009-02-11T00:00:00.000+00:00", 64 "joined": "2023-08-23T00:00:00.000+00:00", 65 "salary": 13579 66 } 67 68 GET Employees 69 [ 70 { 71 "id": "661aac79cf04401110c03516", 72 "firstName": "Maxime", 73 "lastName": "Beugnet", 74 "company": "Google", 75 "headquarters": "Mountain View", 76 "created": "1998-09-04T00:00:00.000+00:00", 77 "joined": "2018-02-12T00:00:00.000+00:00", 78 "salary": 2468 79 }, 80 { 81 "id": "661aac79cf04401110c03518", 82 "firstName": "Tim", 83 "lastName": "Kelly", 84 "company": "MongoDB", 85 "headquarters": "New York", 86 "created": "2009-02-11T00:00:00.000+00:00", 87 "joined": "2023-08-23T00:00:00.000+00:00", 88 "salary": 13579 89 } 90 ]
Note that the employee service sends queries to the company service to retrieve the details of the employees' company.
This confirms that the service registry is doing its job correctly because the URL only contains a reference to the company microservice, not its direct IP and port.
1 private CompanyDTO getCompany(String company) { 2 String url = "http://company-service/api/company/name/"; 3 CompanyDTO companyDTO = restTemplate.getForObject(url + company, CompanyDTO.class); 4 if (companyDTO == null) { 5 throw new EntityNotFoundException("Company not found: ", company); 6 } 7 return companyDTO; 8 }
And voilà! You now have a basic microservice architecture running that is easy to use to kickstart your project.
In this architecture, we could seamlessly integrate additional features to enhance performance and maintainability in
production. Caching would be essential, particularly with a potentially large number of employees within the same
company, significantly alleviating the load on the company service.
The addition of a Spring Cloud Circuit Breaker could also
improve the resiliency in production and a Spring Cloud Sleuth would
help with distributed tracing and auto-configuration.
If you have questions, please head to our Developer Community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB.
Top Comments in Forums
There are no comments on this article yet.