This project is a demonstration of an order-invoice system using modern technologies and patterns. It showcases the implementation of asynchronous order-invoice operations using the outbox message pattern, along with robust logging, database management, and containerization.
- Asynchronous Order-Invoice Operations: Utilizes RabbitMQ for handling asynchronous processing of orders and invoices.
- Outbox Message Pattern: Implements the outbox pattern for reliable message publishing and processing.
- Logging: Logging system for tracking application events and errors.
- Database Management: Uses Prisma ORM with PostgreSQL for data handling and migrations.
- Containerization: Docker and Docker Compose for easy deployment and scaling.
├── docker-compose.yml
├── invoice-service
│ ├── Dockerfile
│ ├── Makefile
│ ├── README.md
│ ├── bun.lockb
│ ├── entrypoint.sh
│ ├── package.json
│ ├── prisma
│ │ └── schema.prisma
│ ├── src
│ │ ├── config
│ │ │ ├── env.ts
│ │ │ └── messaging.ts
│ │ ├── controllers
│ │ │ └── invoice.controller.ts
│ │ ├── index.ts
│ │ ├── interfaces
│ │ │ ├── message-broker.ts
│ │ │ └── outbox.interface.ts
│ │ ├── lib
│ │ │ ├── axios.ts
│ │ │ ├── broker.ts
│ │ │ ├── logger.ts
│ │ │ ├── prisma.ts
│ │ │ └── rabbitmq-broker.ts
│ │ ├── middlewares
│ │ │ ├── _index.ts
│ │ │ ├── exception.middleware.ts
│ │ │ └── logger.middleware.ts
│ │ ├── models
│ │ │ └── index.ts
│ │ ├── routes
│ │ │ ├── _index.ts
│ │ │ └── invoice.routes.ts
│ │ ├── schemas
│ │ │ └── invoice.schemas.ts
│ │ └── services
│ │ ├── index.ts
│ │ ├── invoices
│ │ │ ├── _index.ts
│ │ │ ├── create-invoice.service.ts
│ │ │ ├── delete-invoice.service.ts
│ │ │ ├── invoice.service.ts
│ │ │ ├── payment-invoice.service.ts
│ │ │ ├── query-invoice.service.ts
│ │ │ └── update-invoice.service.ts
│ │ └── messaging
│ │ ├── _index.ts
│ │ ├── invoice-message.consumer.ts
│ │ ├── invoice-message.publisher.ts
│ │ └── order-message.consumer.ts
│ └── tsconfig.json
├── order-service
│ ├── Dockerfile
│ ├── Makefile
│ ├── README.md
│ ├── bun.lockb
│ ├── entrypoint.sh
│ ├── package.json
│ ├── prisma
│ │ ├── schema.prisma
│ │ └── seed.ts
│ ├── src
│ │ ├── config
│ │ │ ├── env.ts
│ │ │ └── messaging.ts
│ │ ├── controllers
│ │ │ ├── order.controller.ts
│ │ │ └── product.controller.ts
│ │ ├── factories
│ │ │ └── product.factory.ts
│ │ ├── index.ts
│ │ ├── interfaces
│ │ │ └── message-broker.ts
│ │ ├── lib
│ │ │ ├── broker.ts
│ │ │ ├── logger.ts
│ │ │ ├── prisma.ts
│ │ │ └── rabbitmq-broker.ts
│ │ ├── middlewares
│ │ │ ├── _index.ts
│ │ │ ├── exception.middleware.ts
│ │ │ └── logger.middleware.ts
│ │ ├── models
│ │ │ └── index.ts
│ │ ├── routes
│ │ │ ├── _index.ts
│ │ │ ├── order.routes.ts
│ │ │ └── product.routes.ts
│ │ ├── schemas
│ │ │ ├── order.schemas.ts
│ │ │ └── product.schemas.ts
│ │ └── services
│ │ ├── index.ts
│ │ ├── messaging
│ │ │ ├── _index.ts
│ │ │ ├── order-message.consumer.ts
│ │ │ └── order-message.publisher.ts
│ │ ├── orders
│ │ │ ├── _index.ts
│ │ │ ├── create-order.service.ts
│ │ │ ├── delete-order.service.ts
│ │ │ ├── order-outbox.service.ts
│ │ │ ├── order.service.ts
│ │ │ ├── query-order.service.ts
│ │ │ └── update-order.service.ts
│ │ ├── outbox
│ │ │ └── outbox.service.ts
│ │ └── products
│ │ ├── _index.ts
│ │ ├── create-product.service.ts
│ │ ├── product.service.ts
│ │ └── query-product.service.ts
│ └── tsconfig.json
-
Clone the repository:
git clone https://github.com/kushadige/order-invoice-demo.git cd order-invoice-demo
-
Start the application using Docker Compose:
docker-compose up --build
- Create an order by sending a POST request to order-service.
- View the order and invoice data in the database.
- Check the logs to see the order and invoice processing. (also check outbox message processing in database)
- View the RabbitMQ management console to see the message queues.
- Stop the application using
Ctrl+C
and remove the containers usingdocker-compose down
. (use --volumes to remove volumes)
This project implements the outbox message pattern to ensure reliable message publishing. Here's a brief overview of how it works:
- When an order is created, it's stored in the database along with a corresponding outbox message.
- A separate process periodically checks for unpublished outbox messages.
- Unpublished messages are sent to RabbitMQ and marked as published in the database.
- If message publishing fails, it will be retried in the next processing cycle.
Order
: Stores order informationInvoice
: Stores invoice data related to ordersProduct
:OutboxMessage
: Stores messages for the outbox pattern
- BASE_URL:
http://localhost:3001
- Endpoint:
/api/orders
- Method:
POST
- Body:
{ "product_codes": [ "PRD-0001", "PRD-0001", "PRD-0001", "PRD-0002", "PRD-0002" ] }
- Response:
{ "message": "Order created successfully", "order": { "orderId": "ORD-0004", "products": [ { "id": 1, "code": "PRD-0001", "name": "Unbranded Steel Tuna", "price": 907.99, "tax": 10.65, "discount": null, "stock": 7 }, { "id": 2, "code": "PRD-0002", "name": "Modern Granite Bacon", "price": 356.99, "tax": 0.45, "discount": null, "stock": 2 } ], "totalAmount": 1363.28739, "totalTaxAmount": 98.30739, "totalDiscountAmount": 0, "orderDate": "2024-10-20T19:44:05.028Z" } }
- Curl:
curl -X POST http://localhost:3001/api/orders -H "Content-Type: application/json" -d '{"product_codes": ["PRD-0001", "PRD-0001", "PRD-0001", "PRD-0002", "PRD-0002"]}'
- BASE_URL:
http://localhost:3001
- Endpoint:
/api/orders/outbox
- Method:
GET
- Response:
[ { "id": 3, "message": "{\"orderId\":\"ORD-0003\",\"products\":[{\"id\":1,\"code\":\"PRD-0001\",\"name\":\"Unbranded Steel Tuna\",\"price\":907.99,\"tax\":10.65,\"discount\":null,\"stock\":10},{\"id\":2,\"code\":\"PRD-0002\",\"name\":\"Modern Granite Bacon\",\"price\":356.99,\"tax\":0.45,\"discount\":null,\"stock\":4}],\"totalAmount\":1363.28739,\"totalTaxAmount\":98.30739,\"totalDiscountAmount\":0,\"orderDate\":\"2024-10-20T19:42:15.739Z\"}", "type": "order", "createdAt": "2024-10-20T19:42:15.744Z", "status": "COMPLETED", "attempts": 0 }, { "id": 4, "message": "{\"orderId\":\"ORD-0004\",\"products\":[{\"id\":1,\"code\":\"PRD-0001\",\"name\":\"Unbranded Steel Tuna\",\"price\":907.99,\"tax\":10.65,\"discount\":null,\"stock\":7},{\"id\":2,\"code\":\"PRD-0002\",\"name\":\"Modern Granite Bacon\",\"price\":356.99,\"tax\":0.45,\"discount\":null,\"stock\":2}],\"totalAmount\":1363.28739,\"totalTaxAmount\":98.30739,\"totalDiscountAmount\":0,\"orderDate\":\"2024-10-20T19:44:05.028Z\"}", "type": "order", "createdAt": "2024-10-20T19:44:05.031Z", "status": "IN_PROGRESS", "attempts": 0 } ]
- Curl:
curl -X GET http://localhost:3001/api/orders/outbox
- BASE_URL:
http://localhost:3001
- Endpoint:
/api/products
- Method:
GET
- Response:
[ { "id": 1, "code": "PRD-0001", "name": "Unbranded Steel Tuna", "price": 907.99, "tax": 10.65, "discount": null, "stock": 4 }, { "id": 2, "code": "PRD-0002", "name": "Modern Granite Bacon", "price": 356.99, "tax": 0.45, "discount": null, "stock": 0 } ]
- Curl:
curl -X GET http://localhost:3001/api/products
- BASE_URL:
http://localhost:3001
- Endpoint:
/api/orders
- Method:
GET
- Response:
[ { "id": 1, "orderId": "ORD-0001", "totalAmount": 300.070979, "orderDate": "2024-10-20T19:33:34.315Z", "invoiceId": null }, { "id": 2, "orderId": "ORD-0002", "totalAmount": 667.1550249999999, "orderDate": "2024-10-20T19:41:28.914Z", "invoiceId": null } ]
- Curl:
curl -X GET http://localhost:3001/api/orders