Skip to content

A demo app to simulate the logistics of fulfilling orders from warehouses

Notifications You must be signed in to change notification settings

jchennales/warehouse-orders

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Demo App to match Orders to Warehouses

Getting Started

This project is a Spring Boot application built with Maven. To get started, ensure you have the following prerequisites installed:

  • Java Development Kit (JDK) 21 or higher
  • Maven 3.9 or higher
  • Docker or Podman

Database

The application uses PostgreSQL + PostGIS as its database. You can run an instance using Podman (or Docker) with the following command:

mkdir -p ./postgis_data && podman run -d --name postgis -e POSTGRES_PASSWORD=X.yz123 \
           -v ./src/main/resources/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:Z \
           -v ./postgis_data:/var/lib/postgresql/data:Z \
           -p 5432:5432 postgis/postgis

This will start a PostgreSQL container with PostGIS extension enabled. The database will be initialized with the schema defined in init-db.sql.

Running the Application

You can run the Spring Boot application using Maven (in the project directory):

mvn spring-boot:run

This will start the application which will connect to the previously started PostGIS DB. The app will have an embedded ActiveMQ Artemis broker for JMS messaging.

(note that the first execution will take a couple of minutes to populate the dummy data in the DB)

Application design concepts

PostgreSQL with the PostGIS extension was chosen for this demo app due to its robust support for geographic data types and spatial queries while also keeping the standard ACID properties of a relational database. ACID compliance is crucial for order processing systems to ensure data integrity and consistency, like not overselling stock for example.

The design of this demo app is that the /orders endpoint accepts orders and processes them asynchronously using JMS listeners. This guarantees that the endpoint is responsive and can handle high throughput. The orders endpoint will only validate the input and attempt to convert the shipping address to a geographic location.

The GeocodingService is a stub that simulates geocoding by generating coordinates within the continental US based on a hash of the address string. This guarantees the same address will always yield the same coordinates. Empty addresses are considered invalid. You can also include "Fail St" in the address to simulate geocoding failures.

If the order passes basic validation, it is persisted in the database with a PENDING status. A JMS message is sent to trigger the warehouse matching process.

The warehouse matching process uses an optimistic algorithm to find the nearest warehouse with sufficient stock. Warehouses are sorted by proximity to the shipping location, and the first warehouse with enough stock is selected. The sorting is very efficient because of the spatial indexing provided by PostGIS and for each warehouse we collect the relevant stock rows in a single query using an IN clause. This reduces the number of queries needed to find a match.

If a matching warehouse is found, the order status is updated to RESERVED. In the same transaction, the available stock is decremented from the warehouse and moved to reserved stock. This ensures that the stock is not oversold. Finally, a JMS message is sent to trigger the payment process.

If no matching warehouse is found, the order status is updated to NOT_PROCURABLE, indicating that the order cannot be fulfilled.

The payment listener is very simple. It simply builds a PaymentRequest from the order details and simulates a payment processing. The mocked payment processing will accept any payment as long as the credit card CVV is not "666". You can use this to simulate payment failures.

If the payment is accepted, the order status is updated to PAYMENT_ACCEPTED. Otherwise, it is updated to PAYMENT_REJECTED. The scope of this demo app ends here, but in a real application, further steps would be taken to prepare the order for shipment. If the order ends up in PAYMENT_REJECTED or remains in RESERVED for too long without payment confirmation, a scheduled cleanup task could vacate the order. Vacating an order means that any reserved stock is released back to available stock, and the order is marked as vacated.

This demo app is provided as a monolithic application for simplicity, but in a real-world scenario, the different components (like order processing, warehouse management, payment processing) would likely be implemented as separate microservices.

Order Processing Flow

graph TD
    %% Primary Flow
    START((START)) --> PENDING
    
    PENDING -- "Warehouse Matching (JMS)" --> MATCHING_LOGIC{Match Result}
    
    MATCHING_LOGIC -- Success --> RESERVED
    MATCHING_LOGIC -- Failure --> NOT_PROCURABLE
    
    RESERVED -- "Payment Call (JMS)" --> PAYMENT_LOGIC{Payment Result}
    
    PAYMENT_LOGIC -- Success --> PAYMENT_ACCEPTED
    PAYMENT_LOGIC -- Failure --> PAYMENT_REJECTED
    
    %% Expiration / Cleanup Flow (Scheduler)
    PAYMENT_REJECTED -- "Cleanup Task (Grace Period)" --> VACATED
    RESERVED -- "Cleanup Task (Timeout)" --> VACATED
    
    %% Future Suggested States
    PAYMENT_ACCEPTED -- "Warehouse Pickup" --> IN_PREPARATION
    IN_PREPARATION -- "Courier Scanned" --> SHIPPED
    SHIPPED -- "Final Delivery" --> DELIVERED
    
    %% Final States
    NOT_PROCURABLE --> END((END))
    VACATED --> END
    DELIVERED --> END

    style PENDING fill:#404040,stroke:#222
    style MATCHING_LOGIC fill:#404040,stroke:#222
    style VACATED fill:#404040,stroke:#222

    style PAYMENT_ACCEPTED fill:#609060,stroke:#333
    style IN_PREPARATION fill:#609060,stroke:#333
    style SHIPPED fill:#609060,stroke:#333
    style DELIVERED fill:#409040,stroke:#333

    style NOT_PROCURABLE fill:#A05454,stroke:#333
    style PAYMENT_REJECTED fill:#A05454,stroke:#333

    style RESERVED fill:#606D60,stroke:#333

    style START fill:#777777,stroke:#333,stroke-width:2px
    style END fill:#777777,stroke:#333,stroke-width:2px
Loading

Test cases

# Inexisting item (not even persisted)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 1212601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Inexisting customer (not even persisted)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1212121,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Incorrect credit card format (not even persisted)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "41112F22233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Incorrect credit card CVV (not even persisted)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "12345",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Incorrect credit card expiry (not even persisted)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "4526",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Bad address (not even persisted) (this triggers the mock fail via Fail St)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "123 Fail St, New York, NY",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "0526",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Correct order    
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Rejected payment (via CVV:666)
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "666",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 2 },
    { "id": 1051, "quantity": 1 }
  ]
}'

# Not procurable
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{
  "customerId": 1,
  "shippingAddress": "700 Pennsylvania Avenue NW, Washington, DC",
  "creditCardNumber": "4111222233334444",
  "creditCardExpiryDate": "1226",
  "creditCardCvv": "123",
  "creditCardOrderMemo": "My stuff",
  "items": [
    { "id": 601, "quantity": 20000000 },
    { "id": 1051, "quantity": 1 }
  ]
}'

About

A demo app to simulate the logistics of fulfilling orders from warehouses

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages