A Spring Boot integration that ingests customer messages, routes them by loyalty tier (gold / silver / base), and forwards them to the correct outgoing ActiveMQ queue — while logging a personalized discount message for each customer. Tier discount rates are backed by a MySQL database and exposed through a secured REST API, with every create/update tracked via an event-driven audit trail.
- Dual ingestion — accepts incoming customer messages either as files dropped into an
messages/inbox/folder, or as messages on theCUSTOMER.INActiveMQ queue. - Tier-based routing — routes each message to an outgoing queue based on the customer's tier.
- Console logging — prints a personalized discount message per processed customer.
- Configurable discounts (bonus) — gold/silver discount rates are stored in a database instead of being hardcoded.
- REST API (bonus) — manage tiers and discount rates over HTTP.
- Basic authentication — write operations on the API require authenticated users.
- Event-driven audit log (bonus) — every tier creation/update is recorded as a domain event, including which user made the change.
- Java 21+
- Docker Desktop
- Maven (or use the included wrapper
./mvnw)
Make sure Docker Desktop is running before you continue — otherwise docker compose will fail with a connection error.
docker compose up -dThis starts two containers:
| Service | Container | Ports | Purpose |
|---|---|---|---|
mysql |
db |
3306 |
Stores tiers/discount rates |
artemis |
message_route |
8161 (web console), 61616 (broker) |
ActiveMQ Artemis message broker |
The username and password for both MySQL and Artemis is demo. If you wish to customize these, create a .env file in the project root — just remember to also update the corresponding Spring Boot environment variables (spring.datasource.* and spring.artemis.* in application.properties) to match.
./mvnw spring-boot:runThe application starts on http://localhost:8080 by default.
Via file:
- Open the
messages/inboxfolder in your project. - Create a new file inside it. The file name doesn't matter, but it must end with the
.jsonextension. - Paste in a message, e.g.:
{
"customer": "bolag 1",
"tier": "gold"
}- Save the file (Ctrl+S) — this is what triggers the message to be picked up and processed.
Via Artemis:
- Open the web console at
http://localhost:8161. - Log in with your
ARTEMIS_USER/ARTEMIS_PASSWORD. - Send a JSON message to the
CUSTOMER.INqueue.
View all tiers — since GET /api/tiers is public, just open it directly in your web browser: http://localhost:8080/api/tiers. This shows a JSON list of all existing tiers and their current discount rates.
Create or update a tier — POST /api/tiers and PATCH /api/tiers/{level} require authentication, so a browser alone isn't enough. Use a tool like Insomnia (or Postman) instead:
- Create a new request in Insomnia, set the method to
POSTorPATCH. - Set the URL, e.g.
http://localhost:8080/api/tiers(POST) orhttp://localhost:8080/api/tiers/gold(PATCH). - Under Auth, choose Basic Auth and log in with one of the users defined in
SecurityConfig(see Authentication below), e.g.anna/ninja123. - Set the body to JSON:
- For
POST(creating a new tier), include both fields:
- For
{
"level": "bronze",
"discountPercentage": 5
}- For
PATCH(updating an existing tier's discount), onlydiscountPercentageis needed:
{
"discountPercentage": 35
}- Send the request.
Incoming messages (from either messages/inbox/ or CUSTOMER.IN) are JSON with the following shape:
{
"customer": "bolag 1",
"tier": "gold"
}The tier field is optional. If it's missing, the customer is treated as a base customer:
{
"customer": "bolag 3"
}| Tier | Message |
|---|---|
| gold | Thank you <customer> for being a gold-customer. Your discount is 30% |
| silver | Thank you <customer> for being a silver-customer. Your discount is 10%! |
| base | Thank you <customer> for being a base-customer. Your discount is 0% |
<customer> is replaced with the value of the customer field from the incoming message (e.g. bolag 1).
The project follows a domain-driven, event-based design:
- The
tierpackage is organized around the domain (domain,application,infrastructure,web) rather than technical layers alone, keeping business logic decoupled from persistence and transport concerns. - Changes to tiers (creation, discount updates) are modeled as domain events (
TierCreatedEvent,DiscountUpdatedEvent), which are picked up asynchronously byTierEventListenerto build an audit trail — rather than writing audit logic directly into the service layer. - Message ingestion and routing (
messagespackage) is handled via Apache Camel (CamelRouteBuilder), which reads from both themessages/inboxfolder and theCUSTOMER.INqueue and routes to the correct outgoing queue based on tier.
Base path: /api/tiers
| Method | Endpoint | Auth required | Description |
|---|---|---|---|
GET |
/api/tiers |
No | List all tiers and their current discount rates |
POST |
/api/tiers |
Yes (Basic Auth) | Create a new tier (e.g. bronze) with a discount rate |
PATCH |
/api/tiers/{level} |
Yes (Basic Auth) | Update the discount rate for an existing tier |
See Getting Started → Explore the tier API for step-by-step instructions on testing these endpoints.
The API uses HTTP Basic Auth backed by an in-memory user store (SecurityConfig). GET /api/tiers is public; POST and PATCH require authentication.
Local test users:
| Username | Password |
|---|---|
anna |
ninja123 |
emilia |
agent007 |
filippa |
bananer4 |
These are hardcoded for local development/demo purposes only — do not ship these credentials to a real environment.
Every tier mutation publishes a domain event (TierCreatedEvent, DiscountUpdatedEvent), which TierEventListener picks up and persists as a TierEvent — recording:
- the tier
level - the event type (
TIER_CREATED/DISCOUNT_UPDATED) - a JSON payload with the relevant details (new discount, or old vs. new discount)
- the username of the authenticated user who made the change (read from
SecurityContextHolder)
This gives full traceability of who changed what and when, without coupling the write path directly to persistence logic.
- Java / Spring Boot
- Apache Camel (message ingestion & tier-based routing)
- Spring Security (HTTP Basic Auth)
- Spring Data JPA (tier persistence)
- Apache ActiveMQ Artemis (message broker, run via Docker)
- MySQL (tier/discount persistence, run via Docker)
- Docker Compose (local infrastructure)
- Lombok
- Jackson (
tools.jackson/ObjectMapperfor event payload serialization)
This project was built based on the following requirements:
- Ingest customer messages via file (
messages/inbox/) or ActiveMQ (CUSTOMER.IN). - Route messages to
CUSTOMER.GOLD.OUT,CUSTOMER.SILVER.OUT, orCUSTOMER.BASE.OUTbased on tier. - Log a personalized discount message per processed message.
- (Bonus) Store discount rates in a database (via Docker).
- (Bonus) Expose a REST API to
GET/update discount rates and add new tiers. - (Extra, self-initiated) Secure write endpoints with Basic Auth and track all tier changes via an event-driven audit log.