A store that sells a variety of amazing products. What it does:
- Manage products via REST API
- Notify other consumer services of newly created products via Kafka
(The diagram shows a desired system design where Kafka consumers are different services. Extra services such as Zookeeper are opted out for clarity.)
The project includes three modules:
bom
: a Bill of Materials that manages shared dependenciesproduct
: a Spring Boot app that provides CRUD for productsconsumer
: a Spring Boot app that is a consumer of theproduct
app
At the module directories of bom
, product
, and consumer
, run this command
mvn clean install
At the project directory, run this command
docker compose up -d
Create a product
curl --location 'http://localhost:50001/products' \
--header 'Content-Type: application/json' \
--data '{
"name": "pasta",
"price": 42
}'
Response
{
"id": "a2dc29c5e0d54b5f8651e4c602a77b77",
"name": "pasta",
"price": 42.0,
"createdBy": "0ab54fa913ed423cb267f5feb301b268",
"createdAt": "2023-04-28T09:06:21.92219219",
"updatedBy": "d5e500593dc644ba8a181f6c140226b5",
"updatedAt": "2023-04-28T09:06:21.92219219"
}
Show logs of consumer
to see if the message has been received.
docker logs -f awesome-store-consumer-1
The log should contain
Received message in group 'consumer': {"id": "a2dc29c5e0d54b5f8651e4c602a77b77", "name": "pasta", "price": 42.0}
Awesome store chooses API First approach using Open API 3.0 and Open API Maven Generator to boost API development and allow foreseeing how the product looks like. The generated code can be overridden via Mustache templates such as data transfer object. The REST API can be viewed via Swagger UI.
Whenever a product is created, a message is sent to Kafka for other services to consume. This way other services will be notified of the newly created product.
Kafka is used to send notifications to other microservices asynchronously in real time. It has these advantages:
- Scalability:
- A Kafka cluster can span across multiple data centers
- A single topic can place partitions on different brokers
- Multiple producers and consumers
- Messages can be collected from different sources
- Consumer group allows pulling messages from different partitions in parallel
- Fault tolerance and high availability:
- Data is copied and spread on different brokers
- If one broker fails, data won't be lost
- High performance
As Product's Kafka messages tend to evolve by development's needs, Confluent Avro is used to version schemas of Kafka messages. Schemas are stored in Kafka's log files and indices are stored in Avro's in-memory storage. For example, ProductMessage's schema:
{
"type": "record",
"name": "ProductMessage",
"namespace": "com.emeraldhieu.awesomestore.product",
"fields":
[
{
"name": "id",
"type": "string"
},
{
"name": "name",
"type": "string"
},
{
"name": "price",
"type": "double"
}
]
}
Liquibase supports revisioning, deploying and rolling back database changes. On top of that, it allows initializing data from CSV for demonstrative purpose.
Like Lombok, Mapstruct is a code generator library that supports mapping between entities and DTOs without writing boilerplate code. A significant benefit is that mappers don't need unit tests because there's no code to test!
GET /products
Parameters | Description | Format |
---|---|---|
sortOrders |
Sort products | column1,direction|column2,direction |
Some examples of sortOrders
:
createdAt,desc
updatedAt,desc|createdBy,asc
curl --location 'http://localhost:50001/products?sortOrders=updatedAt%2Cdesc%7CcreatedBy%2Casc'
[
{
"id": "0a5eb04756f54776ac7752d3c8fae45b",
"name": "spaghetti",
"price": 3.14,
"createdBy": "20825389f950461b8766c051b9182dd4",
"createdAt": "2022-11-27T00:00:00",
"updatedBy": "cca4806536fe4b218c12cdcde4d173df",
"updatedAt": "2022-11-28T00:00:00"
}
]
POST /products
Required parameters
Parameters | Type | Description |
---|---|---|
name |
String | Name |
price |
Double | Price |
curl --location 'http://localhost:50001/products' \
--header 'Content-Type: application/json' \
--data '{
"name": "coke",
"price": 123
}'
{
"id": "a65944903bd94b1dabee196323542ed9",
"name": "coke",
"price": 123.0,
"createdBy": "4b93f05a150d489d949abb71ec0d3c58",
"createdAt": "2023-04-27T00:35:59.378757",
"updatedBy": "87d708c4766f461d8fa718ce50249081",
"updatedAt": "2023-04-27T00:35:59.378757"
}
GET /products/<id>
Parameters | Description | Type |
---|---|---|
id |
Product ID | String |
curl --location 'http://localhost:50001/products/a65944903bd94b1dabee196323542ed9'
{
"id": "a65944903bd94b1dabee196323542ed9",
"name": "coke",
"price": 123.0,
"createdBy": "4b93f05a150d489d949abb71ec0d3c58",
"createdAt": "2023-04-27T00:35:59.378757",
"updatedBy": "87d708c4766f461d8fa718ce50249081",
"updatedAt": "2023-04-27T00:35:59.378757"
}
DELETE /products/<id>
Parameters | Description | Type |
---|---|---|
id |
Product ID | String |
curl --location --request DELETE 'http://localhost:50001/products/a65944903bd94b1dabee196323542ed9'
Response status is 204 with no content
- Postman collections for testing API