| Service | Docker Image | Port |
|---|---|---|
| API Gateway | estatehub/gateway-service | 8080 |
| User Service | estatehub/user-service | 8081 |
| Listing Service | estatehub/listing-service | 8082 |
| Media Service | estatehub/media-service | 8083 |
| Search Service | estatehub/search-service | 8084 |
| Notification Service | estatehub/notification-service | 8085 |
UserService is a microservice responsible for user management and security within the system.
It acts as the central source of truth for user data and is responsible for issuing JWT tokens used by other services.
The service covers the full user lifecycle: registration, account activation, authentication, password reset, and role management.
Communication with other services is handled asynchronously using domain events (Kafka).
- user registration and email uniqueness validation
- account activation using a one-time activation token
- user authentication and JWT token generation
- password reset flow (request / confirm)
- user role management (ADMIN)
- publishing domain events (event-driven architecture)
UserService is structured into clearly separated layers:
- API – REST controllers and DTOs, responsible only for the HTTP contract
- Domain – business logic, domain services, and rules
- Persistence – JPA entities, repositories, and mapping
- Security – security configuration and JWT handling
- Events – domain event publishing
- Config / Exception – technical configuration and global error handling
This separation ensures clarity, testability, and ease of future development.
After registration, a user account is created in an inactive state.
UserService generates a one-time activation token and publishes an event that can be consumed by an external service (e.g. NotificationService) to send an activation email.
Account activation:
- is a one-time operation
- is based on a token with an expiration date
- is required in order to log in
POST /auth/registerPOST /auth/confirm-registrationPOST /auth/loginPOST /auth/password-reset/requestPOST /auth/password-reset/confirm
PATCH /admin/users/{id}/roles
UserService publishes domain events such as:
- user registered
- user activated
- password reset requested / confirmed
Thanks to event-based communication, other services are not directly coupled to the user database.
ListingService is a microservice responsible for managing real estate listings and their lifecycle. It acts as the central source of truth for listing data and exposes both public and authenticated access to listing content.
The service covers the full listing lifecycle: draft creation, content updates, publication, and archiving. Communication with other services is handled asynchronously using domain events (Kafka).
- creation and management of real estate listings
- handling the full listing lifecycle (draft, published, archived)
- versioned storage of listing content
- publication and archiving of listings
- enforcing ownership and role-based access rules
- exposing public read access for published listings
- publishing domain events (event-driven architecture)
ListingService is structured into clearly separated layers:
- API – REST controllers and DTOs, responsible only for the HTTP contract
- Domain – business logic, lifecycle rules, and validations
- Persistence – JPA entities, repositories, and mapping
- Security – ownership and authorization enforcement
- Events – domain event publishing
- Config / Exception – technical configuration and global error handling
This separation ensures clarity, testability, and ease of future development.
Listings move through the following lifecycle:
DRAFT → PUBLISHED → ARCHIVED
- Listings are created as drafts.
- Draft listings are only visible to their owner or ADMIN users.
- Published listings are publicly visible.
- Archived listings are immutable and not publicly visible.
Only the listing owner or an ADMIN user may modify a listing.
ListingService uses a versioned content model.
- Listing metadata and state are stored in
ListingEntity - Listing content is stored as immutable versions in
ListingVersionEntity
Each update creates a new content version, allowing the system to track changes over time and ensure consistency between stored data and public views.
- Public users can access only published listings.
- Owners and ADMIN users can access their own draft listings.
- The service automatically resolves the correct version of content to return based on listing state.
GET /listings/{id}– retrieve a published listing
POST /listings– create a new draft listingPUT /listings/{id}– update listing contentPOST /listings/{id}/publish– publish a draft listingPOST /listings/{id}/archive– archive a listingGET /listings/mine– retrieve listings owned by the current user
ListingService publishes domain events when the public state of a listing changes:
- ListingPublished – when a draft listing is published
- ListingUpdated – when a published listing content changes
- ListingArchived – when a listing is archived
Thanks to event-based communication, other services are not directly coupled to the listing database.
ListingService integrates with MediaService using asynchronous events.
- PhotoUploadedV1
- adds
mediaIdto the current readable listing version
- adds
- PhotoDeletedV1
- removes
mediaIdfrom the listing version
- removes
Photo changes are applied to the version currently used for reads:
- published version for
PUBLISHEDlistings - current draft version for
DRAFTlistings
If a listing is PUBLISHED, photo changes trigger a ListingUpdated event.
ListingService integrates with SearchService to provide fast searching and filtering of published listings.
The integration is event‑driven. Whenever the public state of a listing changes, ListingService publishes events that are consumed by SearchService to update the Elasticsearch index.
- ListingPublishedV1
- ListingUpdatedV1
- ListingArchivedV1
ListingService exposes an administrative endpoint used by SearchService when rebuilding the search index:
GET /admin/listings/published
The endpoint returns paginated data for all published listings including:
- listing metadata
- versioned content
- address data
- price and area
- property attributes
- photo identifiers
This endpoint is intended strictly for search index rebuild
operations and requires the ADMIN role.
MediaService is a microservice responsible for managing media files (photos) associated with real estate listings.
It handles secure upload, storage, processing, and controlled access to listing photos.
The service integrates with external object storage (S3-compatible, MinIO) and communicates with other services using domain events.
It does not store media files in the database – only metadata and references.
- validating uploaded media files (size, content type)
- uploading original photos to object storage
- generating and storing image thumbnails
- managing media metadata
- enforcing ownership and access rules
- exposing presigned URLs for secure access
- publishing media domain events (event-driven architecture)
MediaService follows the same layered architecture as other services:
- API – REST controllers and upload endpoints
- Domain – media rules, ownership verification, and processing logic
- Persistence – media metadata entities and repositories
- Storage – S3-compatible object storage integration (MinIO)
- Security – JWT-based authentication and ownership enforcement
- Events – media event publishing
- Config / Exception – technical configuration and global error handling
- Photos of published listings are publicly readable (via presigned URLs).
- Photos of draft listings are accessible only to the listing owner or ADMIN users.
- Upload and deletion are restricted to listing owners and ADMIN users.
POST /media/listings/{listingId}/photos– upload a photoDELETE /media/photos/{mediaId}– delete a photo
GET /media/listings/{listingId}/photos– list photos for a listingGET /media/photos/{mediaId}– retrieve photo metadata and URLs
MediaService publishes domain events such as:
- PhotoUploadedV1
- PhotoDeletedV1
These events are consumed by ListingService to keep listing photo references consistent.
SearchService is a microservice responsible for searching, filtering, and indexing listings.
It provides a fast read model backed by Elasticsearch.
Unlike other services, SearchService does not own listing data. Instead,
it builds and maintains a search index based on events published by
ListingService.
- maintaining Elasticsearch index of listings
- providing search and filtering API
- processing listing lifecycle events
- supporting full index rebuilds
- exposing administrative diagnostics endpoints
SearchService follows the same layered architecture pattern:
- API -- search controllers and DTOs
- Domain -- search orchestration and indexing logic
- Persistence -- Elasticsearch repositories and documents
- Integration -- Kafka event consumers
- Security -- JWT authentication for administrative endpoints
- Config / Exception -- technical configuration and global error handling
SearchService stores listing documents in Elasticsearch using:
SearchListingDocument
Each document contains:
- listing id
- title and description
- price and currency
- address data
- property attributes (area, rooms, floor)
- photo identifiers
- publication timestamp
Only listings with status PUBLISHED are indexed.
SearchService consumes events from the listing-events Kafka topic.
- ListingPublishedV1 -- creates a new document
- ListingUpdatedV1 -- updates the document
- ListingArchivedV1 -- removes the document
This keeps the search index synchronized with the public state of listings.
GET /search/listings
Supports filtering by:
- full‑text query
- city / country
- price range
- area range
- rooms
- floor
- property type
Also supports:
- pagination
- sorting
GET /search/listings/{listingId}
Returns a single listing document from the search index.
GET /search/admin/index-info
Returns:
- index name
- existence status
- number of indexed documents
Requires ADMIN role.
POST /search/admin/reindex
Triggers a full rebuild of the Elasticsearch index.
Process:
- SearchService calls
ListingService → /admin/listings/published - Fetches all listings page by page
- Recreates Elasticsearch documents
- Rebuilds the search index
Used for:
- disaster recovery
- index rebuilds
- schema changes
NotificationService is a microservice responsible for sending transactional emails based on domain events.
It consumes events from Kafka and delivers email notifications to end users without direct coupling to other services’ databases.
The service is fully event-driven and operates asynchronously.
- consuming domain events from Kafka (
user-events,listing-events) - sending transactional emails (registration, activation, password reset, listing lifecycle)
- maintaining a local read model for resolving
userId → email - ensuring idempotent event processing
- logging notification delivery status (
SENT,FAILED) - safe handling of event reprocessing (at-least-once delivery)
NotificationService follows the same layered architecture pattern as other services:
- Domain – notification orchestration and processing logic
- Persistence – notification logs and user email index
- Integration (Kafka) – event consumers and deserialization
- Mail – Thymeleaf template rendering and SMTP delivery
- Config / Exception – technical configuration and global error handling
The service does not expose public business endpoints and is not routed through the API Gateway.
NotificationService consumes events from the following Kafka topics:
- UserRegisteredV1
- UserActivatedV1
- PasswordResetRequestedV1
- PasswordResetCompletedV1
- ListingPublishedV1
- ListingUpdatedV1
- ListingArchivedV1
Events are deserialized using the shared EventEnvelope from the common-events module.
Each event is processed exactly once using eventId-based idempotency.
Every processed event is stored in the notification_log table.
The log contains:
eventId(unique constraint)eventType- recipient email
- related
userId/listingId - status:
RECEIVED → SENT → FAILED - number of delivery attempts
- error details
- timestamps
This ensures safe retries and operational visibility.
Listing events contain only ownerId, so NotificationService maintains a local read model:
user_email_index
This table is populated from user-related events and allows resolving:
userId → email
This keeps the system fully event-driven and avoids synchronous calls to UserService.
Emails are sent using:
- Spring Mail
- Thymeleaf templates
- SMTP (MailHog in local development)
Transactional emails include:
- registration (activation token)
- account activated
- password reset requested (reset token)
- password reset completed
- listing published
- listing updated
- listing archived
Emails contain tokens for backend confirmation flows and instructions for API usage.
- A business service publishes a domain event.
- Kafka delivers the event to NotificationService.
- The service:
- checks idempotency using
eventId - resolves the recipient email
- renders the appropriate email template
- sends the email via SMTP
- updates the
notification_logstatus
If email delivery fails:
- the notification is marked as
FAILED - error details are stored in the log
- the event can be safely retried without duplication
The project uses local infrastructure managed via Docker Compose.
All required supporting services (database, messaging, search, storage) are defined in docker-compose.yml.
| Component | Purpose | Default Port |
|---|---|---|
| PostgreSQL | Relational database (users, listings, etc.) | 5432 |
| Kafka | Event streaming / async communication | 9092 |
| Elasticsearch | Search & indexing engine | 9200 |
| Kibana | Elasticsearch UI | 5601 |
| Mailhog | SMTP email testing | 8025 |
| MinIO | S3-compatible object storage (media files) | 9000 (API), 9001 (Console) |
Data is persisted using Docker volumes, so container restarts do not remove stored data.
Run from the project root directory:
docker compose up -dCheck status:
docker compose psView logs:
docker compose logs -fStop infrastructure:
docker compose downStop and remove volumes (⚠ deletes data):
docker compose down -vPostgreSQL
docker exec -it estatehub-postgres pg_isready -U estatehubKafka
docker logs estatehub-kafka --tail 50Elasticsearch
curl http://localhost:9200Kibana Open in browser:
http://localhost:5601
MinIO
- API: http://localhost:9000
- Console: http://localhost:9001
Credentials:
minioadmin / minioadmin
Enter Kafka container:
docker exec -it estatehub-kafka shList topics:
kafka-topics.sh --bootstrap-server localhost:9092 --listCreate topic:
kafka-topics.sh --bootstrap-server localhost:9092 --create --topic test --partitions 1 --replication-factor 1Producer:
kafka-console-producer.sh --bootstrap-server localhost:9092 --topic testConsumer (second terminal):
docker exec -it estatehub-kafka sh
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginningdocker exec -it estatehub-kafka shkafka-topics.sh --bootstrap-server localhost:9092 --list/opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic user-events \
--from-beginning/opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic listing-events \
--from-beginning/opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic media-events \
--from-beginningexitThe project consists of multiple Spring Boot microservices. Each service is built as a separate Docker image using Java 25.
All commands must be executed from the project root directory.
docker build -f gateway-service/Dockerfile -t estatehub/gateway-service .docker build -f user-service/Dockerfile -t estatehub/user-service .docker build -f listing-service/Dockerfile -t estatehub/listing-service .docker build -f media-service/Dockerfile -t estatehub/media-service .docker build -f search-service/Dockerfile -t estatehub/search-service .docker build -f notification-service/Dockerfile -t estatehub/notification-service .Assumes infrastructure is already running (
docker compose up -d)
docker run --rm -p 8080:8080 estatehub/gateway-servicedocker run --rm -p 8081:8081 estatehub/user-servicedocker run --rm -p 8082:8082 estatehub/listing-servicedocker run --rm -p 8083:8083 estatehub/media-servicedocker run --rm -p 8084:8084 estatehub/search-servicedocker run --rm -p 8085:8085 estatehub/notification-serviceList images:
docker imagesList containers:
docker ps -aFollow container logs:
docker logs -f <container_name>