|
| 1 | +# Project Structure & Conventions |
| 2 | + |
| 3 | +## Repository Layout |
| 4 | + |
| 5 | +Multi-service monorepo with three services plus shared infrastructure: |
| 6 | + |
| 7 | +```text |
| 8 | +├── unicorn_contracts/ # Contracts Service - property contracts |
| 9 | +├── unicorn_approvals/ # Approvals Service - approval workflow |
| 10 | +├── unicorn_web/ # Web Service - property listings and search |
| 11 | +├── unicorn_shared/ # Global namespaces and shared images stacks |
| 12 | +├── docs/ # Documentation and architecture diagrams |
| 13 | +└── pyproject.toml # Repository root project metadata |
| 14 | +``` |
| 15 | + |
| 16 | +## Service Structure Pattern |
| 17 | + |
| 18 | +Each service follows this structure. Maintain it when adding or modifying services: |
| 19 | + |
| 20 | +```text |
| 21 | +unicorn_<service>/ |
| 22 | +├── Makefile # Canonical build/deploy/test interface |
| 23 | +├── pyproject.toml # Dependencies, pytest and coverage config |
| 24 | +├── uv.lock # Locked dependency versions (committed) |
| 25 | +├── ruff.toml # Lint/format configuration |
| 26 | +├── src/ |
| 27 | +│ ├── <service>_service/ # Lambda handlers and domain code (snake_case dir) |
| 28 | +│ └── requirements.txt # Generated by make build for SAM - do not edit |
| 29 | +├── tests/ |
| 30 | +│ ├── unit/ # Unit tests (+ events/ payloads, conftest.py) |
| 31 | +│ ├── integration/ # Integration tests against deployed stacks |
| 32 | +│ └── pipes/ # Stream/pipe payload fixtures (contracts) |
| 33 | +└── infrastructure/ # All IaC for the service (see below) |
| 34 | +``` |
| 35 | + |
| 36 | +Step Functions ASL definitions live next to the service template (e.g., `unicorn_approvals/infrastructure/approvals-service/property_approval.asl.yaml`). |
| 37 | + |
| 38 | +## Infrastructure Layout |
| 39 | + |
| 40 | +Every service owns its infrastructure under `infrastructure/`: |
| 41 | + |
| 42 | +```text |
| 43 | +infrastructure/ |
| 44 | +├── domain.yaml # Event bus, bus policies, schema registry, |
| 45 | +│ # catch-all rule, SSM exports |
| 46 | +├── <service>-service/ |
| 47 | +│ ├── template.yaml # Lambda, API Gateway, DynamoDB, queues |
| 48 | +│ ├── samconfig.toml # SAM build/deploy configuration |
| 49 | +│ └── api.yaml # OpenAPI specification (REST services) |
| 50 | +├── schema-registry/ |
| 51 | +│ └── <EventName>-schema.yaml # One stack per published event schema |
| 52 | +└── subscriptions/ |
| 53 | + └── <producer>-subscriptions.yaml # Rules on producer buses feeding this service |
| 54 | +``` |
| 55 | + |
| 56 | +Deploy order: shared namespaces first, then per service `domain -> schema -> service`; subscriptions reference both participating domains. `make deploy` and `make delete` encode the correct (reverse) order. |
| 57 | + |
| 58 | +## Code Conventions |
| 59 | + |
| 60 | +- Source directories are snake_case (`contracts_service`, `approvals_service`, `publication_manager_service`, `search_service`) |
| 61 | +- Lambda handler modules are snake_case and describe the trigger (`contract_event_handler.py`) |
| 62 | +- Domain enums and exceptions live beside handlers (`enums.py`, `exceptions.py`) |
| 63 | +- Event schema models live in `src/schema/` |
| 64 | +- Format and lint with ruff before committing (`make format`, `make lint`) |
| 65 | + |
| 66 | +## Resource Naming & Events |
| 67 | + |
| 68 | +- Stack names: `uni-prop-{stage}-{service}` (e.g., `uni-prop-local-contracts`); schema stacks append `-schema-<EventName>` |
| 69 | +- Event buses: `unicorn-{service}-eventbus-${Stage}` (e.g., `unicorn-contracts-eventbus-local`), exported via SSM |
| 70 | +- Event sources use the service namespace value (e.g., `unicorn-contracts`), resolved from the SSM namespace parameters — never hardcode it |
| 71 | +- Canonical event detail types: `ContractStatusChanged` (Contracts), `PublicationApprovalRequested` (Web), `PublicationEvaluationCompleted` (Approvals) |
| 72 | +- SSM parameter contracts: |
| 73 | + - Global namespaces: `/uni-prop/Unicorn{Service}Namespace` |
| 74 | + - Stage-scoped: `/uni-prop/${Stage}/{Service}EventBus`, `...EventBusArn`, `...SchemaRegistryName` |
| 75 | +- Each domain template includes a catch-all rule logging all bus events to CloudWatch for debugging |
| 76 | + |
| 77 | +## Testing Conventions |
| 78 | + |
| 79 | +```text |
| 80 | +tests/ |
| 81 | +├── unit/ # Fast tests, AWS mocked with moto; conftest.py fixtures |
| 82 | +│ └── events/ # Sample event payloads (e.g., create_contract_valid_1.json) |
| 83 | +├── integration/ # Run against a deployed stack |
| 84 | +└── pipes/ # EventBridge/stream/pipe payload fixtures |
| 85 | +``` |
| 86 | + |
| 87 | +- Test event naming: `[action]_[entity]_[condition]_[n].json` (e.g., `create_contract_valid_1.json`) |
| 88 | +- pytest config lives in `pyproject.toml`: coverage over `src/` with an 80% fail-under gate |
| 89 | +- Integration tests require a deployed `local` stage stack |
| 90 | + |
| 91 | +## Development Workflow |
| 92 | + |
| 93 | +When adding a feature: |
| 94 | + |
| 95 | +1. Implement the handler in `src/<service>_service/` following existing patterns |
| 96 | +2. Add or update SAM resources in `infrastructure/<service>-service/template.yaml` using the naming conventions above |
| 97 | +3. Add unit test payloads under `tests/unit/events/` and tests beside existing ones |
| 98 | +4. If the change publishes or consumes a new event: update `infrastructure/schema-registry/` and the consumer's `infrastructure/subscriptions/` |
| 99 | +5. Run `make lint` and `make test` before deploying with `make deploy STAGE=local` |
| 100 | + |
| 101 | +When modifying services, preserve the existing structure — do not reorganize directories without a documented reason. |
0 commit comments