The purpose of this project is to provide a sample implementation of an e-commerce product following Domain-Driven Design (DDD) and Service-Oriented Architecture (SOA) principles.
Programming language is Java with heavy use of Spring Boot, Docker and Kubernetes.
This repository focuses mostly on cross-cutting, infrastructure and deployment concerns.
For the domain and application concepts see the original repository.
Both monolithic and microservices deployments are implemented.
To run the monolithic application:
./gradlew :application:bootRunTo set up and run microservices, see the Docker and Kubernetes sections.
Read more about monoliths vs microservices at https://blog.ttulka.com/good-and-bad-monolith
As the message broker a simple Redis instance could be used with Spring profile redis:
docker run --rm --name redis-broker -p 6379:6379 -d redis:6 redis-server
./gradlew :application:bootRun --args='--spring.profiles.active=redis'Alternatively, RabbitMq could be used as the message broker with Spring profile rabbitmq:
docker run --rm --name rabbitmq-broker -p 5672:5672 -d rabbitmq:3
./gradlew :application:bootRun --args='--spring.profiles.active=rabbitmq'When neither redis not rabbitmq profiles are active, the system will fall-back to use of Spring application events as the default messaging mechanism.
To make the code independent of a concrete messaging implementation and easy to use, Spring application events are used for the internal communication.
In practice, this means that messages are published via EventPublisher abstraction and consumed via Spring's @EventListener.
To make this work, the external messages are re-sent as Spring application events under the hood.
The whole system uses one externalized database with particular tables owned exclusively by services.
In a real-world system this separation would be further implemented by separate schemas/namespaces.
As the database PostgreSQL instance could be used:
docker run --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=secret -d postgres:13Start the application with Spring profile postgres:
./gradlew :application:bootRun --args='--spring.profiles.active=postgres'When the postgres profile is not active, the system will fall-back to use H2 as the default database.
The project is a Gradle multi-project, all sub-projects can be built in a single command:
./gradlew clean buildBuild an image per microservice via Gradle Spring Boot plugin:
./gradlew bootBuildImageTo run the containers:
docker container run --rm -p 8080:8001 ttulka/ecommerce-catalog-service
docker container run --rm -p 8080:8002 ttulka/ecommerce-order-service
docker container run --rm -p 8080:8003 ttulka/ecommerce-cart-service
docker container run --rm -p 8080:8004 ttulka/ecommerce-payment-service
docker container run --rm -p 8080:8005 ttulka/ecommerce-delivery-service
docker container run --rm -p 8080:8006 ttulka/ecommerce-dispatching-service
docker container run --rm -p 8080:8007 ttulka/ecommerce-warehouse-service
docker container run --rm -p 8080:8000 ttulka/ecommerce-portal-serviceActive profiles can be set as follows:
docker container run --rm -e "SPRING_PROFILES_ACTIVE=redis,postgres" -p 8080:8001 ttulka/ecommerce-catalog-serviceBuild NGINX reverse proxy image:
docker build -t ttulka/ecommerce-reverseproxy reverseproxyStart the entire microservices stack:
docker-compose upAccess the Postgres database and init some data:
docker exec -it <containerID> psql -U postgres postgresINSERT INTO categories VALUES
('C1', 'books', 'Books'),
('C2', 'games', 'Games');
INSERT INTO products VALUES
('P1', 'Domain-Driven Design', 'by Eric Evans', 45.00),
('P2', 'Object Thinking', 'by David West', 35.00),
('P3', 'Chess', 'Classic game.', 3.20);
INSERT INTO products_in_categories VALUES
('P1', 'C1'),
('P2', 'C1'),
('P3', 'C2');
INSERT INTO products_in_stock VALUES
('P1', 5),
('P2', 0),
('P3', 1);The NGINX reverse proxy serves as a simple API gateway:
curl localhost:8080/catalog/products
curl localhost:8080/warehouse/stock/5To use local images for development with Minikube, run the following command to use local Docker images registry:
minikube start
eval $(minikube docker-env)Afterwards, build the docker images again for the Minikube's Docker daemon:
./gradlew bootBuildImageCreate deployments:
kubectl apply -f 1-infrastructure.k8s.yml
kubectl apply -f 2-backend-services.k8s.yml
kubectl apply -f 3-frontend-portal.k8s.yml
kubectl apply -f 4-api-gateway.k8s.ymlSet up ports forwarding to access the cluster from your local network:
kubectl port-forward service/reverseproxy 8080:8080Alternatively, you can create an Ingress:
minikube addons enable ingress
kubectl apply -f 5-ingress.k8s.yml
# get the ingress address
kubectl get ingress ecommerce-ingress
# add the address into hosts
sudo cp /etc/hosts hosts.bak
sudo echo -e '\n<ingress-address> ecommerce.local' >> /etc/hosts
# access the application in browser: http://ecommerce.local