property-service — Service Overview
Companion: Summary · Service Template · Naming · 02 Enterprise Architecture · 05 API Design · 06 Data Models · 07 Security & Tenancy
1. One-paragraph mission
property-service is the system of record for the physical inventory every Ghasi Melmastoon tenant sells: the property (hotel, guesthouse, melmastoon), its room types, individual rooms, amenities, photos, policies, and geo-coordinates. It feeds four downstream surfaces: the consumer meta-search index (cross-tenant, read-only), the per-tenant booking site catalog, the Electron desktop backoffice (with offline replication), and the AI orchestrator (for description drafts, photo tagging, and amenity suggestion). It does not own pricing, availability, housekeeping state, or theme visuals.
2. Bounded Context Position
| Field | Value |
|---|---|
| Bounded Context | Property |
| Domain Type | Core |
| Strategic intent | Differentiator: regionally meaningful amenity vocabulary, multi-language fields (Pashto/Dari/Persian/Tajik/EN), PostGIS-backed map, offline-first room state |
| Upstream contexts | Identity, Tenant, File Storage, Lock Integration, Housekeeping, Maintenance |
| Downstream contexts | Inventory, Pricing, Reservation, Search Aggregation, Theme Config (read for slug), BFFs (consumer / tenant-booking / backoffice) |
| Pattern with downstream | Open Host Service (REST + Published Language events) |
| Shared kernel | TenantId, I18nString, MediaRef, AddressLine, GeoPoint, AIProvenance from @ghasi/contracts-melmastoon |
3. Capabilities
| Capability | Notes |
|---|---|
| Property CRUD | Create / read / update / soft-delete with multi-language fields; status machine draft → published → unpublished → archived |
| Property publish gate | Validates rooms ≥ 1, geo present, name + description in default locale, ≥ 1 ready photo, terms accepted |
| Geo location | geography(POINT, 4326); bounding-box and nearby (great-circle) queries; tile-friendly result shapes for Leaflet |
| Room type catalog | King / Twin / Suite / Family / Dorm / Apartment with bed configs and max occupancy |
| Individual rooms | Number, floor, status (active / out_of_order / out_of_service / archived), accessibility flags, lock device binding |
| Amenities | Canonical registry + per-property selection; regional codes (halal kitchen, prayer room, generator, hot-water schedule, women-only floor) |
| Photos | Signed-URL upload via file-storage-service; ordered set per property; per-room-type photos |
| Policies | Property-level overrides for tenant defaults: check-in/out, cancellation, child, smoking, pets, deposit, ID required |
| Room groups | Floor / building / wing for housekeeping routing and operator UX |
| Sync | Exposes change stream to bff-backoffice-service for `/sync/v1/pull |
| AI | Description draft, photo auto-tagging, amenity suggestion, geocode fallback (HITL) |
4. Non-Capabilities (explicitly out of scope)
| Capability | Owned by |
|---|---|
| Per-night availability and holds | inventory-service |
| Rate plans, prices, derivations | pricing-service |
| Live housekeeping state machine (clean / dirty / inspected timeline) | housekeeping-service |
| Maintenance work orders | maintenance-service |
| Reservations, walk-ins, check-in/out | reservation-service |
| Theme tokens, layouts, hero images | theme-config-service |
| Cross-tenant search index and ranker | search-aggregation-service |
| Lock device hardware integration | lock-integration-service |
| File bytes, virus scan, retention | file-storage-service |
| Tax / VAT / locale defaults | tenant-service |
5. Architecture (Clean / Hexagonal)
property-service/
└── src/
├── domain/ # pure: aggregates, VOs, domain events, invariants
│ ├── property/
│ │ ├── Property.ts # Property aggregate root
│ │ ├── PropertyStatus.ts # VO enum + transitions
│ │ ├── PropertyPublished.ts # invariant set for publish
│ │ └── events/ # PropertyCreated, PropertyPublished, ...
│ ├── room-type/
│ │ ├── RoomType.ts
│ │ ├── BedConfig.ts # VO
│ │ └── events/
│ ├── room/
│ │ ├── Room.ts
│ │ ├── RoomStatus.ts
│ │ ├── RoomNumber.ts # VO with normalization rules
│ │ └── events/
│ ├── photo/
│ │ ├── Photo.ts
│ │ ├── PhotoOrder.ts # VO with reorder algebra
│ │ └── events/
│ ├── policy/
│ │ ├── PropertyPolicies.ts # aggregate of overrides
│ │ └── events/
│ ├── amenity/
│ │ ├── AmenityCode.ts # VO matching canonical registry
│ │ └── AmenityRegistry.ts # in-memory canonical list
│ ├── room-group/
│ │ └── RoomGroup.ts
│ └── shared/
│ ├── PropertyId.ts # branded ID `ppt_…`
│ ├── RoomTypeId.ts # `rmt_…`
│ ├── RoomId.ts # `rmu_…`
│ ├── PhotoId.ts # `pht_…`
│ ├── PolicyId.ts # `pol_…`
│ ├── AmenityId.ts # `amn_…`
│ ├── RoomGroupId.ts # `rgp_…`
│ └── errors/
├── application/ # use cases, ports, CQRS handlers
│ ├── ports/
│ │ ├── PropertyRepository.ts
│ │ ├── RoomTypeRepository.ts
│ │ ├── RoomRepository.ts
│ │ ├── PhotoRepository.ts
│ │ ├── PolicyRepository.ts
│ │ ├── RoomGroupRepository.ts
│ │ ├── AmenityRegistry.ts
│ │ ├── EventPublisher.ts # outbox publisher
│ │ ├── FileStoragePort.ts # signed URL request
│ │ ├── GeocodePort.ts
│ │ ├── AIClient.ts
│ │ ├── ReservationLookupPort.ts # check active reservations on a room
│ │ └── Clock.ts
│ ├── commands/ # one file per command + handler
│ ├── queries/ # query handlers
│ ├── sagas/ # nothing local; this service hosts choreography only
│ └── policies/ # publish-eligibility, OOO-eligibility
├── infrastructure/ # adapters
│ ├── postgres/
│ │ ├── PropertyRepositoryPg.ts
│ │ ├── RoomTypeRepositoryPg.ts
│ │ ├── RoomRepositoryPg.ts
│ │ ├── PhotoRepositoryPg.ts
│ │ ├── PolicyRepositoryPg.ts
│ │ ├── RoomGroupRepositoryPg.ts
│ │ ├── OutboxRepositoryPg.ts
│ │ ├── InboxRepositoryPg.ts
│ │ └── tenant-context.ts # SET LOCAL app.tenant_id
│ ├── pubsub/
│ │ ├── EventPublisherPubSub.ts
│ │ └── consumers/ # tenant.created, housekeeping.maintenance_required, etc.
│ ├── filestorage/
│ │ └── FileStorageHttpAdapter.ts
│ ├── geocode/
│ │ ├── GoogleMapsGeocodeAdapter.ts
│ │ └── OpenCageGeocodeAdapter.ts
│ ├── ai/
│ │ └── AIClientHttpAdapter.ts
│ ├── reservation/
│ │ └── ReservationLookupHttpAdapter.ts
│ └── cache/
│ └── PropertyCacheRedis.ts
└── presentation/ # controllers, DTOs, OpenAPI
├── http/
│ ├── PropertiesController.ts
│ ├── RoomTypesController.ts
│ ├── RoomsController.ts
│ ├── PhotosController.ts
│ ├── PoliciesController.ts
│ ├── AmenitiesController.ts
│ ├── RoomGroupsController.ts
│ ├── GeoController.ts
│ ├── SyncController.ts # /internal/v1/property/changes
│ └── HealthController.ts
└── dto/
├── PropertyDto.ts
├── RoomTypeDto.ts
├── RoomDto.ts
├── PhotoDto.ts
└── PolicyDto.ts
Dependency rule: presentation → application → domain, infrastructure → application (adapters implement ports). domain imports nothing outside itself and @ghasi/contracts-melmastoon shared-kernel VOs.
6. Tech Stack
| Layer | Choice |
|---|---|
| Language / runtime | TypeScript on Node 20 LTS |
| HTTP framework | NestJS (Fastify adapter) |
| ORM / DB driver | pg + kysely (typed query builder); migrations via node-pg-migrate |
| Validation | zod for DTOs and event payloads |
| Messaging | GCP Pub/Sub (transactional outbox) |
| Cache | Memorystore (Redis 7) |
| Geo | PostGIS 3.x extension |
| Logging | pino JSON |
| Tracing | OpenTelemetry → Cloud Trace |
| Metrics | OpenTelemetry → Cloud Monitoring |
7. SLOs
| SLI | Target |
|---|---|
| API p95 latency (read) | ≤ 80 ms |
| API p95 latency (write) | ≤ 250 ms |
| Availability | 99.9 % monthly |
| Outbox publish lag (p95) | ≤ 2 s |
| Search projection freshness | ≤ 5 s after publish |
| RLS leak | 0 |
| Photo upload signed-URL acquisition | p95 ≤ 200 ms |
8. Quotas / limits per tenant (defaults; overridable by plan)
| Resource | Default cap |
|---|---|
| Properties | 100 |
| RoomTypes per property | 50 |
| Rooms per property | 2 000 |
| Photos per property | 200 |
| Photos per room type | 30 |
| Bulk room create batch size | 200 per call |
| Geo nearby radius | 100 km |
| Geo bbox area | 5 000 km² |
Plan-level overrides come from tenant-service via tenant.plan_changed.v1.
9. Risks Snapshot
| Risk | Mitigation |
|---|---|
| Cross-tenant leak via geo bbox | RLS + WHERE tenant_id enforced by query layer; audited test |
| Stale projection in search | dedicated outbox publisher SLO + replay endpoint |
| OOO during occupied stay | domain guard + reservation lookup port |
| AI photo tagging hallucinated amenity | HITL-only; never auto-applied |
| PostGIS misconfiguration | infra-as-code + smoke test in deploy pipeline |
Full register: SERVICE_RISK_REGISTER.
10. Definition of Ready / Done
- Ready (per story): AC, NFRs, OpenAPI delta, event schema delta, tenancy implications, AI provenance, sync impact, observability, runbook entry.
- Done (per story): tests in pyramid (unit, integration, contract, sync, AI), tenant-isolation spec passes, outbox spec passes, OpenAPI lint passes, dashboards updated, ADR if cross-cutting.
11. Glossary
| Term | Meaning |
|---|---|
Property | Sellable physical site (hotel, guesthouse, melmastoon) within a tenant. |
RoomType | Sellable category (King, Twin, Suite, Family, Dorm, Apartment) with bed config + max occupancy. |
Room | Individual physical room (numbered) belonging to one RoomType. |
Amenity | Canonical tag from the platform registry (wifi, halal_kitchen, …). |
Policy | Property-level override of tenant defaults (check-in/out, cancellation, …). |
RoomGroup | Logical grouping of rooms (floor, wing, building). |
OOO | Out of Order — temporarily unsellable, e.g., maintenance. |
OOS | Out of Service — long-term decommissioned. |
Publish | Status transition that exposes the property to search and booking surfaces. |