Main is QuintoAndar's Spring Boot monolith. Its public API and internal admin pages use separate controllers, but both call the same business module. A change to a proposal or property should follow the same rules from either interface.
Read this from top to bottom: both interfaces enter business, which uses storage, event publishing, and HTTP adapters. Scheduling has a separate cache; Big Agent owns a separate database.
+-------------------------------------------+
| End Users |
| - Mobile/Web Clients (JSON APIs) |
| - Admin Users (HTML + JSON) |
+---------------------+---------------------+
|
| HTTPS via Ingress (Host header)
| - api-mobile.dev.quintoandar.com.br
| - user.dev.quintoandar.com.br
v
+---------------------------------+----------------------------------+
| Main Monolith |
| (Spring Boot, implementation/*) |
+-------------------+-----------------------+------------------------+
| |
REST/JSON | | HTML + Admin APIs
(JAX-RS) | | (JAX-RS + FreeMarker)
v v
+------------+---+ +------+-------------+
| mobile-api | | view |
| | | |
| - Controllers | | - Controllers |
| - DTOs, OpenAPI| | - Templates (FM) |
| - AuthZ: OPA | | - AuthZ: @Roles |
| scopes, | | Allowed + OPA |
| @RolesAllowed| | |
+------+---------+ +---------+---------+
| delegates | delegates
| |
v v
+-------------------------+---------------+---------------+------------------+
| business |
| - Domain services (orchestration) |
| - Domain entities / repositories (Hibernate/JPA) |
| - Configuration (URLParameters) and integration policies |
| - Publishes domain events (Eventuate outbox) |
+-----------+--------------------+---------------------+---------------------+
| | |
| JPA/Hibernate | Domain Events | HTTP Clients (adapters)
v v v
+-----+-----+ +------+-------+ +----+-----------------------+
| MySQL | | Outbox | | adapters |
| (storage) | | (Eventuate | | - remoting/ clients |
+-----+-----+ | Tram) | | (HttpRepository) |
^ +------+-------+ | - publishers/mappers |
| | | - URLParameters |
| | +-----------+---------------+
| | |
| | | HTTP (JSON)
| | v
| +-----+------+ +---------------------------+
| | Broker |<------->| backend-services |
| | SQS/SNS/ | | (apps/libs monorepo) |
| | Kafka | | |
| +-----+------+ | +-----------------------+ |
| ^ | | big-agent | |
| | | | - api (REST) | |
| (sqs-consumers) | | - worker (SQS/Kafka) | |
| ^ | | - remuneration | |
| | | | - incentiveengine | |
| +-----+------+ | | - core/adapter | |
| | sqs- | | | - PostgreSQL | |
| | consumers | | +----------+------------+ |
| | health:8888| | ^ HTTP (Feign) |
| +-----+------+ | | (mobile-api-ro) |
| +---+----------------------+
| |
| <--- HTTP (BigAgentApiController) -------+
|
| availability/cache
v
+-----+------+ +----------------------+
| schedule- | <------> | Cache: |
| planner | | - Redis (forno/prod)|
| (API+cache)| | - Guava (local dev) |
+------------+ +----------------------+
Observability: Actuator :9464, Prometheus metrics
Security: OPA scopes, Keycloak roles
Local dev: Ingress, Localstack, MySQL, Kafka
Big Agent: PostgreSQL, Flyway, OpenFeign, Eventuate Tram, Debezium
Other services connect through HTTP and events. The useful part of this design is the consistency of those connections.
One pattern for HTTP calls
Service addresses live in URLParameters. Clients use HttpRepository for requests, serialization, and error handling. Each integration gets its own contract and adapter, while sharing the mechanics.
That gives failures a familiar shape: which endpoint was called, which status came back, and what the response contained. An engineer moving between integrations has less client-specific code to learn.
Save the event with the change
A property price update can affect search and notifications. Saving the price and publishing the event separately creates a gap: the database can commit while the broker is unavailable.
Main uses Eventuate Tram's transactional outbox:
- Save the business change and its event in one MySQL transaction.
- Let a relay forward the stored event to the broker.
- Let consumers update their own state.
Publisher methods require an existing transaction. Delivery is at least once, so consumers must handle duplicates. The relay still needs to recover and process its backlog after a failure.
Scheduling has a different cost. Computing available visits requires property, agent, and appointment data. schedule-planner caches that data in Redis, with periodic refreshes and updates from database changes. Availability can lag; the booking confirmation flow must handle a slot that is no longer free.
Big Agent shows how a service can move out of Main. It owns agent-property relationships and commissions in its own PostgreSQL database. Contract events trigger earnings calculations. Narrow HTTP lookups connect it back to Main.
That separation leaves dependencies between the services, but gives each one ownership of its writes. Extracting a service still requires deciding who owns the data and how the other side learns it changed.