A microservices monorepo built on Dapr, following a centralized configuration architecture for all Dapr components, configurations, and subscriptions.
- Overview
- Repository Structure
- Dapr Architecture
- Quick Start
- Running Services
- Dapr Configuration
- Service Ports
- Development Workflow
- Component Management
- Configuration Management
- Troubleshooting
- Best Practices
This repository contains multiple microservices that communicate via Dapr's building blocks (pub/sub, state management, service invocation). All Dapr-related configuration is centralized in the /dapr directory, following the architecture defined in .cursor/rules.md.
- Centralized Configuration: All Dapr components, configs, and subscriptions live in
/dapr - Service Isolation: Each service implements business logic only
- Standardized Ports: Each service has unique Dapr ports to avoid conflicts
- Environment-Driven: Services use environment variables for configuration
- Multi-App Support: Run all services together or individually
rmbrain/
├── dapr/ # Centralized Dapr configuration
│ ├── components/ # All Dapr components (pubsub, state stores, etc.)
│ ├── config/ # Global Dapr configuration (tracing, metrics, resiliency)
│ └── subscriptions/ # Declarative pub/sub subscriptions
│
├── bff_service/ # Backend for Frontend
├── cas_service/ # Canonical Audit Service
├── client_service/ # Client Data Service
├── document_service/ # Document Management Service
├── interaction_service/ # Interaction Service
├── policy_service/ # Canonical Policy Service
├── product_service/ # Product Data Service
├── relationship_service/ # Relationship Service
├── riskprofile_service/ # Risk Profile Service
├── rmbrain-mainapp/ # Main Application
├── task_service/ # Task Service
│
├── dapr.yaml # Multi-app Dapr configuration (runs all services)
├── .cursor/ # Cursor IDE rules and configuration
└── README.md # This file
All Dapr resources are centralized in /dapr:
- Components (
/dapr/components/): Pub/sub, state stores, bindings, secrets - Config (
/dapr/config/): Tracing, metrics, resiliency policies - Subscriptions (
/dapr/subscriptions/): Declarative pub/sub subscriptions
Each service has its own dapr.yaml file that:
- References centralized components:
../dapr/components - References centralized config:
../dapr/config/global-config.yaml - Defines service-specific settings (ports, environment variables, command)
- Consistency: All services use the same pub/sub and state store components
- Maintainability: Update components once, affects all services
- Environment Parity: Same configuration across dev, staging, production
- Compliance: Follows
.cursor/rules.mdarchitecture guidelines
- Dapr CLI: Install from dapr.io
- Python 3.11+: Required for all services
- PostgreSQL: For services that require a database
- Redis (optional): For production pub/sub and state store
- uv (recommended): Python package manager
-
Set up databases (REQUIRED before running services):
# Run the database setup script ./scripts/setup_databases.sh # Or see scripts/SETUP_DATABASES.md for manual setup
This creates all 9 PostgreSQL databases and initializes schemas.
-
Initialize Dapr (if not already done):
dapr init
-
Verify Dapr installation:
dapr --version dapr list
-
Start Redis (if using Redis components):
# Using Docker docker run -d -p 6379:6379 redis:latest # Or use Dapr's default Redis (started by dapr init)
Prerequisites:
- Set up databases (see Initial Setup step 1 above)
- Install dependencies for all services:
# Install dependencies for each service
for dir in bff_service cas_service client_service document_service interaction_service policy_service product_service relationship_service riskprofile_service rmbrain-mainapp task_service; do
echo "Installing dependencies in $dir"
(cd "$dir" && uv sync)
doneRun all services simultaneously using the root dapr.yaml:
# From repository root
dapr run -f dapr.yamlThis will:
- Start all 11 services with their Dapr sidecars
- Use centralized components from
/dapr/components - Apply global config from
/dapr/config/global-config.yaml - Assign unique ports to each service (see Service Ports)
Note: This is useful for integration testing and local development of the full system.
Important:
- Ensure all service dependencies are installed before running (see Setting Up a Service)
- Commands use
uv run python -m uvicornwhich ensures Python from the local virtual environment is used - Each service has its own virtual environment (
.venv), anduv runautomatically uses the correct environment
Run a single service for development/debugging:
# Navigate to service directory
cd <service-name>
# Run with service's dapr.yaml
dapr run -f dapr.yamlExample:
cd task_service
dapr run -f dapr.yamlThe service's dapr.yaml will:
- Reference
../dapr/components(centralized) - Reference
../dapr/config/global-config.yaml(centralized) - Use service-specific ports and environment variables
For more control, run Dapr manually:
dapr run \
--app-id <service-id> \
--app-port 8000 \
--dapr-http-port <http-port> \
--dapr-grpc-port <grpc-port> \
--components-path ../dapr/components \
--config ../dapr/config/global-config.yaml \
-- <command>Example for task service:
dapr run \
--app-id cds-task \
--app-port 8000 \
--dapr-http-port 3510 \
--dapr-grpc-port 60011 \
--components-path ../dapr/components \
--config ../dapr/config/global-config.yaml \
-- uv run python -m uvicorn cds_task.main:app --host 0.0.0.0 --port 8000Components are defined in /dapr/components/:
pubsub.yaml: In-memory pub/sub (development)rmbrain-pubsub.yaml: Redis pub/sub (production)statestore.yaml: In-memory state store (development)rmbrain-statestore.yaml: Redis state store (production)
Services reference components by name in their code:
pubsuborrmbrain-pubsubfor pub/sub operationsstatestoreorrmbrain-statestorefor state operations
The component name must match the metadata.name in the component YAML file.
Global Dapr configuration is in /dapr/config/global-config.yaml:
- Tracing: OpenTelemetry integration
- Metrics: Prometheus metrics
- HTTP Pipeline: Middleware (rate limiting, CORS)
- Pub/Sub Features: Routing, dead letter queues
- Access Control: Service-to-service authorization policies
Resiliency policies are in /dapr/config/resiliency.yaml:
- Retry Policies: Automatic retry for failed requests
- Circuit Breakers: Prevent cascading failures
- Timeouts: Request timeout configuration
Each service has unique Dapr ports to avoid conflicts:
| Service | App Port | Dapr HTTP | Dapr gRPC |
|---|---|---|---|
| bff-service | 8000 | 3500 | 60001 |
| cas-audit | 8001 | 3501 | 60002 |
| cds-client | 8002 | 3502 | 60003 |
| cds-document | 8003 | 3503 | 60004 |
| cds-interaction | 8004 | 3504 | 60005 |
| cps-policy | 8005 | 3505 | 60006 |
| cds-product | 8006 | 3506 | 60007 |
| cds-relationship | 8007 | 3507 | 60008 |
| cds-riskprofile | 8008 | 3508 | 60009 |
| rmbrain-mainapp | 8009 | 3509 | 60010 |
| cds-task | 8010 | 3510 | 60011 |
Note: gRPC ports use the 60000 series to avoid conflicts with Dapr's internal services which use the 50000 series.
Use Dapr service invocation to call services:
# Call a service via Dapr
curl http://localhost:<dapr-http-port>/v1.0/invoke/<app-id>/method/<endpoint>Example:
# Call task service health endpoint
curl http://localhost:3510/v1.0/invoke/cds-task/method/health
# Call policy service authorize endpoint
curl -X POST http://localhost:3505/v1.0/invoke/cps-policy/method/api/v1/authorize \
-H "Content-Type: application/json" \
-d '{"actor": {...}, "action": "view_task", ...}'-
Navigate to service directory:
cd <service-name>
-
Install dependencies (REQUIRED before running):
uv sync # or pip install -r requirements.txtImportant: Services will fail to start with "Failed to spawn: uvicorn" if dependencies aren't installed.
-
Set up database (if required):
# Create database createdb <database-name> # Run migrations # (check service README for specific instructions)
-
Configure environment:
- Edit
dapr.yamlfor service-specific settings - Or create
.envfile (if service supports it)
- Edit
-
Run the service:
dapr run -f dapr.yaml
-
Create component YAML in
/dapr/components/:apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: my-component namespace: default spec: type: <component-type> version: v1 metadata: - name: <setting> value: <value>
-
Reference it in service code by the
metadata.name
- Edit
/dapr/config/global-config.yaml - Changes apply to all services on next restart
- For service-specific config, add to service's
dapr.yamlenvsection
- Create service directory in repository root
- Create
dapr.yamlwith:- Unique ports (check existing services)
- Reference to
../dapr/components - Reference to
../dapr/config/global-config.yaml
- Add service to root
dapr.yamlfor multi-app support
cd services/<service-name>
uv run pytest-
Start all services:
dapr run -f dapr.yaml
-
Run integration tests (if available):
# From repository root or test directory pytest tests/integration/
Development (in-memory):
pubsub.yaml- In-memory pub/substatestore.yaml- In-memory state store
Production (Redis):
rmbrain-pubsub.yaml- Redis pub/subrmbrain-statestore.yaml- Redis state store
To switch from in-memory to Redis:
- Update service code to use
rmbrain-pubsubinstead ofpubsub - Or update component file to use Redis type
- Ensure Redis is running
- Use descriptive names:
rmbrain-pubsub,rmbrain-statestore - Include namespace:
namespace: default - Document component purpose in comments
This repository uses a hybrid approach for database configuration:
- Development: Database URLs are defined in
dapr.yamlfiles (root and service-level) with localhost defaults - Production: Override database URLs using
.envfiles in each service directory (not committed to git)
All services with databases have DATABASE_URL defined in:
- Root
dapr.yaml- Used when running all services together (dapr run -f dapr.yaml) - Service
dapr.yaml- Used when running individual services
Development defaults (localhost):
postgresql://postgres:postgres@localhost:5432/<database_name>(standard PostgreSQL)postgresql+asyncpg://postgres:postgres@localhost:5432/<database_name>(async PostgreSQL)
-
Copy the example file:
cd <service-name> cp .env.example .env
-
Update with production credentials:
# Edit .env file DATABASE_URL=postgresql://user:password@prod-host:5432/database_name -
The service will automatically load
.envfile (viapydantic_settings)
Important:
.envfiles are excluded from git (via.gitignore)- Never commit production credentials
- Use
.env.exampleas a template
| Service | Database Name |
|---|---|
| cas-audit | cas_audit |
| cds-client | cds_client |
| cds-document | cds_document |
| cds-interaction | interaction_db |
| cds-product | cds_product |
| cds-relationship | relationship_db |
| cds-riskprofile | riskprofile_db |
| rmbrain-mainapp | rmbrain_mainapp |
| cds-task | cds_task |
Services use environment variables for configuration:
Required Variables (set in dapr.yaml):
APP_ID: Service identifierAPP_PORT: Application port (usually 8000)DAPR_HTTP_PORT: Dapr HTTP port (unique per service)DAPR_GRPC_PORT: Dapr gRPC port (unique per service)
Service-Specific Variables:
DATABASE_URL: Database connection string (see Database Configuration above)DAPR_PUBSUB_NAME: Pub/sub component nameLOG_LEVEL: Logging level
-
Via Environment Variables:
export DATABASE_URL="postgresql://user:pass@localhost/db" dapr run -f dapr.yaml
-
Via .env File (if service supports it):
# Create .env in service directory DATABASE_URL=postgresql://user:pass@localhost/db -
Via dapr.yaml: Edit the
envsection in service'sdapr.yaml
-
Install dependencies first (IMPORTANT):
# Navigate to service directory cd <service-name> # Install dependencies using uv uv sync # Or using pip pip install -r requirements.txt
Error:
Failed to spawn: uvicornusually means:- Dependencies aren't installed (run
uv syncin service directory) - Virtual environment doesn't exist (run
uv syncto create it) - Using
python -m uvicornensures the correct Python from the venv is used
- Dependencies aren't installed (run
-
Check Dapr is running:
dapr list
-
Check ports are available:
# Check if port is in use lsof -i :<port>
-
Check component files exist:
ls -la ../dapr/components/
-
Check config file exists:
ls -la ../dapr/config/global-config.yaml
-
Verify component path:
- Should be
../dapr/componentsfrom service directory - Or
./dapr/componentsfrom repository root
- Should be
-
Check component YAML syntax:
# Validate YAML yamllint dapr/components/*.yaml
-
Check component name matches:
- Component
metadata.namemust match what service code uses
- Component
If you see port conflicts (especially gRPC ports):
-
Check which service is using the port:
dapr list lsof -i :<port>
-
Stop conflicting service:
dapr stop --app-id <service-id>
-
Note: gRPC ports are in the 60000 series to avoid Dapr's internal 50000 series ports
-
Or change port in service's dapr.yaml
-
Check pub/sub component is loaded:
# Check Dapr logs dapr logs --app-id <service-id>
-
Verify topic names match:
- Publisher and subscriber must use same topic name
-
Check pub/sub component type:
- In-memory pub/sub doesn't persist across restarts
- Use Redis for production
-
Check target service is running:
dapr list
-
Verify app-id matches:
- Service invocation uses
app-idfromdapr.yaml
- Service invocation uses
-
Check Dapr HTTP port:
- Use correct Dapr HTTP port for the target service
- ✅ DO: Keep all components in
/dapr/components/ - ✅ DO: Use consistent naming conventions
- ✅ DO: Document component purpose
- ❌ DON'T: Create service-specific component directories
- ❌ DON'T: Duplicate components across services
- ✅ DO: Use centralized global config
- ✅ DO: Override via environment variables when needed
- ✅ DO: Document service-specific requirements
- ❌ DON'T: Hardcode configuration in code
- ❌ DON'T: Create service-specific config files (unless necessary)
- ✅ DO: Use unique ports for each service
- ✅ DO: Document port assignments
- ✅ DO: Check port availability before starting
- ❌ DON'T: Reuse ports across services
- ❌ DON'T: Use ports below 3500 (reserved for Dapr)
- ✅ DO: Run services individually during development
- ✅ DO: Use multi-app mode for integration testing
- ✅ DO: Test with both in-memory and Redis components
- ❌ DON'T: Modify centralized components without testing
- ❌ DON'T: Commit local environment-specific changes
- ✅ DO: Use environment variables for configuration
- ✅ DO: Expose
/healthendpoint - ✅ DO: Use Dapr SDK for pub/sub and state
- ✅ DO: Follow event-driven architecture
- ❌ DON'T: Bypass Dapr for inter-service communication
- ❌ DON'T: Implement custom retry logic (use Dapr resiliency)
- Dapr Documentation: https://docs.dapr.io
- Dapr Python SDK: https://github.com/dapr/python-sdk
- Repository Rules: See
.cursor/rules.mdfor architecture guidelines - Service Documentation: See individual service
README.mdfiles
- Check service-specific README files in
/<service-name>/README.md - Review Dapr logs:
dapr logs --app-id <service-id> - Check Dapr status:
dapr listanddapr components list - Review centralized config:
/dapr/README.md
Last Updated: 2025-01-20
Dapr Version: 1.x
Python Version: 3.11+