DEV Community

Roberto de Vargas Neto
Roberto de Vargas Neto

Posted on • Edited on

Infrastructure as Code: Deploying a Financial Ecosystem with Docker Compose

Hey folks!

In the previous post, I introduced the overview of My Broker B3. Today we'll talk about the foundation that supports the entire ecosystem: the infrastructure.

For a microservices system of this complexity, manually configuring each database, message broker and monitoring tool would be unmanageable. The solution was to use Docker Compose to create a reproducible local environment that faithfully replicates the needs of a distributed system.


🗺️ The Infrastructure Map

The docker-compose.yml orchestrates 12 containers organized in 5 layers:

┌─────────────────────────────────────────────┐
│           RELATIONAL LAYER (SQL)            │
│  identity-db  wallet-db  order-db  asset-db │
│     MySQL        MySQL    MySQL     MySQL   │
│                  b3-core-db (PostgreSQL)    │
├─────────────────────────────────────────────┤
│              NOSQL LAYER                    │
│           broker-mongodb (Mongo 6.0)        │
├─────────────────────────────────────────────┤
│              CACHE LAYER                    │
│    broker-asset-cache    b3-market-cache    │
│         Redis Alpine          Redis Alpine  │
├─────────────────────────────────────────────┤
│             MESSAGING LAYER                 │
│    kafka (KRaft)        rabbitmq (AMQP)     │
├─────────────────────────────────────────────┤
│           OBSERVABILITY LAYER               │
│        prometheus            grafana        │
└─────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

🏗️ Design Decision: Domain Isolation

The most important infrastructure decision was data isolation by domain. Instead of a monolithic database, each microservice has its own instance:

# Each service has its own database — failures are isolated
broker-identity-db:
  image: mysql:8.0
  ports:
    - '3306:3306'  # identity

broker-wallet-db:
  image: mysql:8.0
  ports:
    - '3307:3306'  # wallet

broker-order-db:
  image: mysql:8.0
  ports:
    - '3308:3306'  # orders

broker-asset-db:
  image: mysql:8.0
  ports:
    - '3309:3306'  # assets

b3-core-db:
  image: postgres:15
  ports:
    - '5432:5432'  # B3 core
Enter fullscreen mode Exit fullscreen mode

Why does this matter? If the orders database has a problem, the identity service keeps running. Failures stay isolated in their domain.


📦 Cache: Isolated Instances by Context

Two Redis instances with distinct purposes:

broker-asset-cache:
  image: redis:7.0-alpine
  ports:
    - '${REDIS_BROKER_ASSET_PORT}:6379'  # prices for the broker

b3-market-cache:
  image: redis:7.0-alpine
  ports:
    - '${REDIS_B3_MARKET_PORT}:6379'    # prices for the matching engine
Enter fullscreen mode Exit fullscreen mode

The b3-market-sync-api writes to b3-market-cache and the b3-matching-engine-api reads from it to decide whether to execute or reject an order. The separation ensures that broker-side problems don't affect the B3 side and vice versa.


📡 Messaging: Two Brokers, Two Purposes

Apache Kafka — Internal Event Bus

Configured in modern KRaft mode (no Zookeeper), lighter and more stable for local development:

kafka:
  image: apache/kafka:3.7.0
  environment:
    KAFKA_PROCESS_ROLES: broker,controller
    KAFKA_LISTENERS: EXTERNAL://0.0.0.0:9092,INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
    KAFKA_ADVERTISED_LISTENERS: EXTERNAL://localhost:9092,INTERNAL://finance-kafka:29092
    KAFKA_CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
Enter fullscreen mode Exit fullscreen mode

Used for the ecosystem's internal topics:

  • assets-market-data-v1 — asset quotes
  • order-events-v1 — order lifecycle events

RabbitMQ — Broker ↔ B3 Integration

rabbitmq:
  image: rabbitmq:3-management
  ports:
    - '5672:5672'    # AMQP
    - '15672:15672'  # Management UI
Enter fullscreen mode Exit fullscreen mode

Used exclusively for communication between the broker and the B3 simulator:

  • mq-broker-to-b3 — orders sent to the matching engine
  • mq-b3-to-broker — execution feedback

Why two messaging systems? Kafka is ideal for event streams with replay and multiple consumers. RabbitMQ is ideal for work queues with point-to-point delivery guarantees. Each where it makes the most sense.


📊 Observability from Day One

prometheus:
  image: prom/prometheus:latest
  volumes:
    - ./config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
  ports:
    - '9090:9090'

grafana:
  image: grafana/grafana:latest
  ports:
    - '3000:3000'
Enter fullscreen mode Exit fullscreen mode

All Spring Boot microservices export metrics via /actuator/health and /actuator/prometheus. Prometheus collects, Grafana visualizes. From the very first service that starts, we already have observability.


🔐 Security: No Hardcoded Secrets

All sensitive configuration lives in a .env file (ignored by Git):

# docker-compose.yml — references only
broker-identity-db:
  environment:
    MYSQL_DATABASE: ${BROKER_IDENTITY_DB_NAME}
    MYSQL_USER: ${BROKER_DB_USER}
    MYSQL_PASSWORD: ${BROKER_DB_PASS}
    MYSQL_ROOT_PASSWORD: ${BROKER_DB_ROOT_PASS}
Enter fullscreen mode Exit fullscreen mode

A .env.example is committed as a template, but actual values never enter the repository.


🚀 How to Run

cp .env.example .env
# fill in the variables in .env

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

A single command brings up all 12 containers. The development environment is identical for anyone who clones the repository.


What's Next?

With the infrastructure running, the next post shows the first microservice: broker-market-data-api — a Python service that fetches real quotes from Brapi, persists them in MongoDB and publishes to Kafka.


🔎 About the Series

⬅️ Previous Post: Building a Microservices Ecosystem

➡️ Next Post: Market Data Integrator: Consuming Real-Time Data with Python, MongoDB and Kafka

📘 Series Index: Series Roadmap


Links:

Top comments (0)