Skip to main content

Overview

The Orders Service is the core transactional service in Argos Mesh that manages the product catalog and processes customer orders. It handles CRUD operations for products, inventory management, and publishes events to RabbitMQ for downstream processing by other services.
The Orders Service runs on port 8080 and connects to PostgreSQL for data persistence and RabbitMQ for event-driven communication.

Architecture

Service Responsibilities

Product Management

Create, read, update, and delete product records with validation

Order Processing

Handle product sales with stock validation and IP tracking

Event Publishing

Publish product and sales events to RabbitMQ after transaction commit

Security Integration

Check Redis blacklist to block suspicious IP addresses

REST API Endpoints

The ProductController exposes the following RESTful endpoints:

Product Operations

@GetMapping("/{id}")
public ResponseEntity<ProductResponse> getProductById(@PathVariable Long id)
Endpoint: GET /orders/products/{id}Retrieves a product by its ID.Response: ProductResponse with product details

Core Components

Product Entity

The Product entity represents a product in the database:
@Entity
@Table(name = "products")
public class Product {
    @Id
    @Column(name = "product_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productID;
    
    @NotNull
    @NotBlank
    @Column(name = "product_name", nullable = false)
    private String productName;
    
    @NotNull
    @Positive
    @Column(name = "product_price", nullable = false)
    private BigDecimal productPrice;
    
    @NotNull
    @PositiveOrZero
    @Column(name = "product_stock")
    private Integer productStock;
}
Key Fields:
  • productID: Auto-generated primary key
  • productName: Required, non-blank product name
  • productPrice: Must be positive decimal value
  • productStock: Non-negative integer for inventory count

Product Repository

The repository includes a custom method for pessimistic locking during sales:
public interface ProductRepository extends JpaRepository<Product, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT p FROM Product p WHERE p.id = :id")
    Optional<Product> findByIdForUpdate(Long id);
}
The findByIdForUpdate method uses pessimistic write locking to prevent race conditions during concurrent sales transactions.

Product Service Implementation

The ProductServiceImpl class contains the core business logic:
1

IP Blacklist Check

When processing a sale, the service first checks if the client IP is blacklisted in Redis:
if (Boolean.TRUE.equals(redisTemplate.hasKey(BLACKLIST_PREFIX + ipAddress))) {
    throw new SecurityException("Access Denied: Your IP is blocked for suspicious behavior");
}
2

Pessimistic Locking

The service retrieves the product with a pessimistic write lock:
Product product = productRepository.findByIdForUpdate(id)
    .orElseThrow(() -> new ProductNotFoundException("Product not found"));
3

Stock Validation

It validates that sufficient stock is available:
if (product.getProductStock() < quantity) 
    throw new NotSuficientStockException("The stock is not sufficient");
4

Stock Deduction

The product stock is decremented:
product.setProductStock(product.getProductStock() - quantity);
5

Event Publishing

Finally, it publishes a ProductSoldInternalEvent for the Sentinel service:
eventPublisher.publishEvent(
    new ProductSoldInternalEvent(id, quantity, ipAddress, LocalDateTime.now())
);

Event Publishing

The ProductMessagePublisher handles asynchronous message publishing to RabbitMQ:
@Component
public class ProductMessagePublisher {
    private final RabbitTemplate rabbitTemplate;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleProductCreated(ProductCreatedInternalEvent event) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_NAME,
                RabbitMQConfig.RK_PRODUCTS,
                event.response());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleProductSold(ProductSoldInternalEvent event) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_NAME,
                RabbitMQConfig.RK_SALES,
                event);
    }
}
The @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) annotation ensures that events are only published to RabbitMQ after the database transaction successfully commits, maintaining data consistency.

RabbitMQ Configuration

The service configures the following RabbitMQ resources:
public static final String QUEUE_SALE = "argos.sales.queue";
public static final String QUEUE_PRODUCTS = "argos.products.mgmt.queue";
public static final String EXCHANGE_NAME = "shop.exchange";
public static final String RK_SALES = "shop.event.sold";
public static final String RK_PRODUCTS = "shop.event.product.#";

Queues and Bindings

Sales Queue

Queue: argos.sales.queueRouting Key: shop.event.soldReceives ProductSoldInternalEvent messages consumed by the Sentinel service

Products Queue

Queue: argos.products.mgmt.queueRouting Key: shop.event.product.#Receives product lifecycle events (create, update, delete)

Event Schema

ProductSoldInternalEvent

public record ProductSoldInternalEvent(
    Long productID,
    Integer quantity,
    String ipAddress,
    LocalDateTime timeStamp
) {}
This event is published when a product is sold and contains:
  • productID: The ID of the product that was sold
  • quantity: Number of units purchased
  • ipAddress: Client IP address for DDoS detection
  • timeStamp: Timestamp of the sale

Database Configuration

The service connects to PostgreSQL with the following configuration:
spring.datasource.url=jdbc:postgresql://db:5432/shop_db
spring.datasource.username=user_shop
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update

Error Handling

The service implements custom exceptions for common error scenarios:

ProductNotFoundException

Thrown when a product ID doesn’t exist

NotSuficientStockException

Thrown when insufficient inventory is available

SecurityException

Thrown when a blacklisted IP attempts a purchase

Integration Points

1

PostgreSQL Database

Stores product catalog and inventory data with ACID guarantees
2

Redis Cache

Checks IP blacklist maintained by the Sentinel service
3

RabbitMQ

Publishes events to the shop.exchange for consumption by Sentinel and other services

Next Steps

Sentinel Service

Learn how Sentinel monitors traffic from Orders

Message Broker

Explore the RabbitMQ event infrastructure

Build docs developers (and LLMs) love