This is a simple microservices demo that showcases database synchronization between two services using NATS for event communication. The system demonstrates key microservices patterns including event-driven architecture, database per service, and eventual consistency.
The system implements a user management system with two separate services that maintain synchronized data using events:
┌──────────────┐ ┌─────────┐ ┌────────────────────┐
│ User Service├────────►│ NATS ├────────►│Notification Service│
│ (Postgres) │ events │ Server │ sub │ (MongoDB) │
└──────────────┘ └─────────┘ └────────────────────┘
The system consists of two microservices:
-
User Service (Port 8080)
- Uses PostgreSQL database
- Handles user creation and updates
- Publishes events when user data changes
-
Notification Service (Port 8081)
- Uses MongoDB database
- Subscribes to user events
- Maintains a synchronized copy of user data
- Go 1.16 or later
- PostgreSQL
- MongoDB
- NATS Server
- Start the NATS Server:
nats-server- Create PostgreSQL database and table:
createdb users_db
psql users_db < user-service/schema.sql- Start MongoDB:
mongod- Configure the services:
- Update database connection strings in both services if needed
- Default PostgreSQL connection: localhost:5432
- Default MongoDB connection: mongodb://localhost:27017
- Default NATS connection: localhost:4222
- Start the User Service:
cd user-service
go run main.go- Start the Notification Service:
cd notification-service
go run main.go-
GET /users- List all userscurl http://localhost:8080/users
Response:
[ { "id": 1, "name": "Shariar Hossain", "email": "shariar99@example.com", "updated_at": "2025-05-25T10:00:00Z" } ] -
POST /users- Create a new usercurl -X POST http://localhost:8080/users \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe", "email": "john@example.com" }'
Response:
{ "id": 1, "name": "John Doe", "email": "john@example.com", "updated_at": "2025-05-25T10:00:00Z" } -
PUT /users/{id}- Update a usercurl -X PUT http://localhost:8080/users/1 \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe Updated", "email": "john.updated@example.com" }'
Response:
{ "id": 1, "name": "John Doe Updated", "email": "john.updated@example.com", "updated_at": "2025-05-25T10:01:00Z" }
-
GET /notifications- List all notificationscurl http://localhost:8081/notifications
Response:
[ { "user_id": 1, "name": "John Doe Updated", "email": "john.updated@example.com", "updated_at": "2025-05-25T10:01:00Z" } ] -
GET /notifications/user/{id}- Get user notification datacurl http://localhost:8081/notifications/user/1
Response:
{ "user_id": 1, "name": "John Doe Updated", "email": "john.updated@example.com", "updated_at": "2025-05-25T10:01:00Z" }
-
When a user is created or updated in the User Service:
- Data is first saved in PostgreSQL
- An event is published to NATS with the following format:
{ "type": "user_created", // or "user_updated" "payload": { "id": 1, "name": "John Doe", "email": "john@example.com", "updated_at": "2025-05-25T10:00:00Z" } }
-
The Notification Service:
- Subscribes to the "user.events" topic on NATS
- Receives the events in real-time
- Updates its MongoDB database to maintain a synchronized copy
-
User Service (PostgreSQL)
- Primary source of user data
- Handles CRUD operations
- Maintains data integrity with constraints (e.g., unique email)
- Schema:
CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP );
-
Notification Service (MongoDB)
- Maintains a denormalized copy of user data
- Optimized for read operations
- Uses upsert operations for atomic updates
- Collection structure:
{ "user_id": 1, "name": "John Doe", "email": "john@example.com", "updated_at": "2025-05-25T10:00:00Z" }
- Loose Coupling: Services communicate through events, not direct calls
- Independent Scaling: Each service can be scaled independently
- Technology Freedom: Each service uses the best database for its needs
- Resilience: Services can continue working even if others are temporarily down
- Eventually Consistent: Data stays synchronized across services automatically
- Duplicate email addresses are prevented by PostgreSQL constraints
- Failed events can be replayed from NATS if needed
- Each service handles its own errors independently
This implementation demonstrates key microservices patterns including:
- Event-Driven Architecture
- Database per Service
- Eventually Consistent Data
- Service Independence
- Polyglot Persistence (PostgreSQL + MongoDB)
You can check if services are running:
# Check NATS Server
curl http://localhost:8222/varz
# Check PostgreSQL
psql -U postgres -d users_db -c "SELECT version();"
# Check MongoDB
mongosh --eval "db.serverStatus()"-
Service Won't Start
- Check if the required ports are available (8080, 8081, 4222)
- Verify database connections
- Check NATS server is running
-
Data Not Syncing
- Verify NATS connection in both services
- Check event publishing in User Service logs
- Check event subscription in Notification Service logs
-
Database Connection Issues
- PostgreSQL: Check credentials and database existence
- MongoDB: Verify MongoDB service is running
- NATS: Ensure NATS server is running and accessible
-
Watch NATS traffic:
nats sub "user.events" -s http://localhost:4222 -
Monitor PostgreSQL queries:
psql -U postgres users_db -c "SELECT * FROM users;" -
Check MongoDB data:
mongosh "mongodb://localhost:27017/notifications_db" --eval "db.user_notifications.find()"
-
Add new fields:
- Update PostgreSQL schema
- Modify User model
- Update event payload
- Update MongoDB schema
- Update handlers in both services
-
Add new events:
- Define new event type in shared package
- Add publisher in User Service
- Add subscriber in Notification Service
- Always validate input data
- Handle database errors gracefully
- Implement proper logging
- Use transactions where necessary
- Follow idempotency patterns
- Implement retry mechanisms for failed operations