In this article, we will walk through the creation of a scalable microservices architecture for an e-commerce application using Java Spring Boot and Spring Cloud. The architecture includes several services such as Config Server, Eureka Server, API Gateway, Auth Service, Order Service, Inventory Service, and Notification Service with Kafka.
Project Structure
The project is organized into the following structure:ecommerce-microservices ├── config-server ├── eureka-server ├── api-gateway ├── auth-service ├── order-service ├── inventory-service ├── notification-service ├── docker-compose.yml (optional) └── README.md
Config Server
The Config Server manages external configurations for all microservices.config-server/pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>config-server</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
config-server/src/main/resources/application.yml server: port: 8888 spring: cloud: config: server: git: uri: https://github.com/your-repo/config-repo
config-server/src/main/java/com/example/configserver/ConfigServerApplication.java package com.example.configserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
Why Use Config Server?
Config Server provides a centralized place to manage external properties for applications across all environments. It uses a Git repository to store configuration files, making it easy to version and manage configurations.How to Deploy and Run Config Server
To deploy the Config Server, ensure you have a Git repository with configuration files. Then, run the application using:mvn spring-boot:runThis will start the Config Server on port 8888.
Eureka Server
The Eureka Server is a service registry for locating services.eureka-server/pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>eureka-server</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
eureka-server/src/main/resources/application.yml server: port: 8761 eureka: client: register-with-eureka: false fetch-registry: false instance: hostname: localhost
eureka-server/src/main/java/com/example/eurekaserver/EurekaServerApplication.java package com.example.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
Why Use Eureka Server?
Eureka Server acts as a discovery server for registering and locating microservices. This helps in load balancing and makes it easier to scale services dynamically.How to Deploy and Run Eureka Server
To deploy the Eureka Server, run the application using:mvn spring-boot:runThis will start the Eureka Server on port 8761.
API Gateway
The API Gateway routes requests to appropriate services.api-gateway/pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>api-gateway</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
api-gateway/src/main/resources/application.yml server: port: 8080 spring: application: name: api-gateway eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: security: oauth2: resourceserver: jwt: jwk-set-uri: http://localhost:9000/oauth2/default/.well-known/jwks.json
api-gateway/src/main/java/com/example/apigateway/ApiGatewayApplication.java package com.example.apigateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; @SpringBootApplication @EnableEurekaClient @EnableResourceServer public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
api-gateway/src/main/java/com/example/apigateway/config/SecurityConfig.java package com.example.apigateway.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; @Configuration @EnableResourceServer public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/h2-console/**").permitAll() .anyRequest().authenticated() .and() .csrf().disable() .headers().frameOptions().disable(); } }
Why Use API Gateway?
The API Gateway handles requests by routing them to the appropriate microservice. It provides a single entry point for the client and helps in securing and managing requests efficiently.How to Deploy and Run API Gateway
To deploy the API Gateway, run the application using:mvn spring-boot:runThis will start the API Gateway on port 8080.
Auth Service
The Auth Service handles authentication and authorization using OAuth2.auth-service/pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>auth-service</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
auth-service/src/main/resources/application.yml server: port: 9000 spring: application: name: auth-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: datasource: url: jdbc:h2:mem:testdb driverClassName: org.h2.Driver username: sa password: password spring: h2: console: enabled: true spring: jpa: hibernate: ddl-auto: update database-platform: org.hibernate.dialect.H2Dialect
auth-service/src/main/java/com/example/authservice/AuthServiceApplication.java package com.example.authservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; @SpringBootApplication @EnableEurekaClient @EnableAuthorizationServer public class AuthServiceApplication { public static void main(String[] args) { SpringApplication.run(AuthServiceApplication.class, args); } }
auth-service/src/main/java/com/example/authservice/config/AuthorizationServerConfig.java package com.example.authservice.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client-id") .secret("{noop}client-secret") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(36000); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); } }
auth-service/src/main/java/com/example/authservice/config/WebSecurityConfig.java package com.example.authservice.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean @Override protected UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build()); return manager; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest() .authenticated() .and() .formLogin() .permitAll(); } }
Why Use Auth Service?
The Auth Service handles user authentication and authorization using OAuth2. It provides secure access to resources by issuing JWT tokens.How to Deploy and Run Auth Service
To deploy the Auth Service, run the application using:mvn spring-boot:runThis will start the Auth Service on port 9000.
Order Service
The Order Service manages orders within the e-commerce application.order-service/pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>order-service</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
order-service/src/main/resources/application.yml server: port: 9001 spring: application: name: order-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: datasource: url: jdbc:h2:mem:testdb driverClassName: org.h2.Driver username: sa password: password spring: h2: console: enabled: true spring: jpa: hibernate: ddl-auto: update database-platform: org.hibernate.dialect.H2Dialect spring: kafka: bootstrap-servers: localhost:9092 consumer: group-id: order-group
order-service/src/main/java/com/example/orderservice/OrderServiceApplication.java package com.example.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
order-service/src/main/java/com/example/orderservice/model/Order.java package com.example.orderservice.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String product; private int quantity; private double price; // Getters and setters }
order-service/src/main/java/com/example/orderservice/repository/OrderRepository.java package com.example.orderservice.repository; import com.example.orderservice.model.Order; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface OrderRepository extends JpaRepository<Order, Long> { }
order-service/src/main/java/com/example/orderservice/controller/OrderController.java package com.example.orderservice.controller; import com.example.orderservice.model.Order; import com.example.orderservice.repository.OrderRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/orders") public class OrderController { @Autowired private OrderRepository orderRepository; @Autowired private KafkaTemplate<String, String> kafkaTemplate; private static final String TOPIC = "order-topic"; @PostMapping public Order createOrder(@RequestBody Order order) { Order savedOrder = orderRepository.save(order); kafkaTemplate.send(TOPIC, "Order created: " + savedOrder.getId()); return savedOrder; } @GetMapping("/{id}") public Order getOrder(@PathVariable Long id) { return orderRepository.findById(id).orElse(null); } }
Why Use Order Service?
The Order Service manages all operations related to orders. It uses Kafka to send notifications whenever an order is created.How to Deploy and Run Order Service
To deploy the Order Service, run the application using:mvn spring-boot:runThis will start the Order Service on port 9001.
Inventory Service
The Inventory Service manages product inventory within the e-commerce application.inventory-service/pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>inventory-service</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
inventory-service/src/main/resources/application.yml server: port: 9002 spring: application: name: inventory-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: datasource: url: jdbc:h2:mem:testdb driverClassName: org.h2.Driver username: sa password: password spring: h2: console: enabled: true spring: jpa: hibernate: ddl-auto: update database-platform: org.hibernate.dialect.H2Dialect spring: kafka: bootstrap-servers: localhost:9092 consumer: group-id: inventory-group
inventory-service/src/main/java/com/example/inventoryservice/InventoryServiceApplication.java package com.example.inventoryservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class InventoryServiceApplication { public static void main(String[] args) { SpringApplication.run(InventoryServiceApplication.class, args); } }
inventory-service/src/main/java/com/example/inventoryservice/model/Inventory.java package com.example.inventoryservice.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Inventory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String product; private int quantity; // Getters and setters }
inventory-service/src/main/java/com/example/inventoryservice/repository/InventoryRepository.java package com.example.inventoryservice.repository; import com.example.inventoryservice.model.Inventory; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface InventoryRepository extends JpaRepository<Inventory, Long> { }
inventory-service/src/main/java/com/example/inventoryservice/controller/InventoryController.java package com.example.inventoryservice.controller; import com.example.inventoryservice.model.Inventory; import com.example.inventoryservice.repository.InventoryRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/inventory") public class InventoryController { @Autowired private InventoryRepository inventoryRepository; @Autowired private KafkaTemplate<String, String> kafkaTemplate; private static final String TOPIC = "inventory-topic"; @PostMapping public Inventory addInventory(@RequestBody Inventory inventory) { Inventory savedInventory = inventoryRepository.save(inventory); kafkaTemplate.send(TOPIC, "Inventory added: " + savedInventory.getId()); return savedInventory; } @GetMapping("/{id}") public Inventory getInventory(@PathVariable Long id) { return inventoryRepository.findById(id).orElse(null); } }
Why Use Inventory Service?
The Inventory Service manages all operations related to inventory. It uses Kafka to send notifications whenever inventory is updated.How to Deploy and Run Inventory Service
To deploy the Inventory Service, run the application using:mvn spring-boot:runThis will start the Inventory Service on port 9002.
Communication Between Services
Microservices communicate using REST APIs and Apache Kafka for asynchronous messaging. Services are registered with Eureka and communicate via Eureka Server.Testing
Unit and integration tests are implemented using Spring Boot Test.Example test class: order-service/src/test/java/com/example/orderservice/OrderServiceApplicationTests.java package com.example.orderservice; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class OrderServiceApplicationTests { @Test void contextLoads() { } }
Directory Structure
Here's the complete directory structure for the project:ecommerce-microservices/ ├── api-gateway/ │ ├── src/main/java/com/example/apigateway/ApiGatewayApplication.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── auth-service/ │ ├── src/main/java/com/example/authservice/AuthController.java │ ├── src/main/java/com/example/authservice/AuthServiceApplication.java │ ├── src/main/java/com/example/authservice/SecurityConfig.java │ ├── src/main/java/com/example/authservice/User.java │ ├── src/main/java/com/example/authservice/UserRepository.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── config-server/ │ ├── src/main/java/com/example/configserver/ConfigServerApplication.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── eureka-server/ │ ├── src/main/java/com/example/eurekaserver/EurekaServerApplication.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── inventory-service/ │ ├── src/main/java/com/example/inventoryservice/InventoryController.java │ ├── src/main/java/com/example/inventoryservice/Inventory.java │ ├── src/main/java/com/example/inventoryservice/InventoryRepository.java │ ├── src/main/java/com/example/inventoryservice/InventoryServiceApplication.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── notification-service/ │ ├── src/main/java/com/example/notificationservice/KafkaConsumerConfig.java │ ├── src/main/java/com/example/notificationservice/KafkaListenerService.java │ ├── src/main/java/com/example/notificationservice/NotificationServiceApplication.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── order-service/ │ ├── src/main/java/com/example/orderservice/OrderController.java │ ├── src/main/java/com/example/orderservice/Order.java │ ├── src/main/java/com/example/orderservice/OrderRepository.java │ ├── src/main/java/com/example/orderservice/OrderServiceApplication.java │ ├── src/main/resources/application.yml │ └── pom.xml ├── docker-compose.yml (optional) └── README.md
0 comments:
Post a Comment