-
Notifications
You must be signed in to change notification settings - Fork 1
background design decisions
The four ADRs in docs/adrs/ document architectural decisions accepted by the 360Ghar team. All four were written on 2026-05-08 (ADR 004 revised 2026-06-06). They describe a target architecture the codebase is migrating toward - the current type-based layout in app/api/, app/services/, app/models/ is what ADR 001 proposes to move away from.
Active contributors: Saksham, Ravi
File: docs/adrs/001-domain-module-structure.md
Problem. The current type-based (layered) directory layout organizes code by technical role rather than business domain. A change to the booking flow touches bookings.py in endpoints, booking.py in services, models in models/, schemas in schemas/, and MCP tools in mcp/tool_ops/. With 40+ service files and 30+ schema modules in flat directories, finding the relevant file for a domain concept requires knowing the project's naming conventions. MCP tool logic lives in app/mcp/tool_ops/ while the same domain's REST endpoint lives elsewhere, leading to duplicated or divergent authorization.
Decision. Migrate to a domain-driven module structure under app/modules/{domain}/ where each module is self-contained: api.py, service.py, repository.py, models.py, schemas.py, mcp.py, enums.py, and a tests/ directory. Sub-domains (like PM under property) nest further. app/shared/ holds cross-domain contracts.
Status. Accepted but not implemented. app/modules/ and app/shared/ exist as reserved placeholders. The current concrete homes (app/api, app/services, app/models, app/repositories, app/mcp) remain canonical until a domain is migrated.
File: docs/adrs/002-repository-protocol-interfaces.md
Problem. The repository pattern is incomplete. app/repositories/ has BaseRepository[T], PropertyRepository, and PropertyQueryBuilder, but most services (booking.py, visit.py, flatmates.py, user.py) write raw SQLAlchemy queries directly against AsyncSession. This couples services to SQLAlchemy, makes unit testing impossible without a real database, and duplicates query logic (filter by user ID, paginate, load relationships) across services.
Decision. Introduce typing.Protocol-based repository interfaces that define the data access contract for each domain. Services depend on protocols, not on concrete SQLAlchemy repositories. Concrete implementations wrap SQLAlchemy and live in the repository layer. This creates a clean seam for swapping persistence or inserting a caching layer.
Status. Accepted. BaseRepository and PropertyRepository already follow this pattern for the property domain; other domains have not been migrated.
File: docs/adrs/003-event-driven-side-effects.md
Problem. Business operations trigger side effects (push notifications, emails, cache invalidation, analytics) that are executed inline as sequential in-service function calls. The notification dispatcher blocks the primary operation's response - if email or FCM is slow, the HTTP response to the client is delayed. Error handling is inconsistent across call sites. There is no ordering guarantee, no deduplication, no replay, and unit tests must mock every side effect.
Decision. Introduce a simple in-process event bus that decouples primary business operations from their side effects. Operations emit events; registered handlers react asynchronously in background tasks. Handler failures are isolated from the primary operation. Optional Redis pub/sub extension for multi-process dispatch.
Status. Accepted. The SSEEventBus in app/core/sse.py is a per-user pub/sub for real-time UI updates, not the general-purpose event bus this ADR describes. The notification dispatcher (app/services/notification_dispatcher.py) still runs inline. Migration to the event bus is pending.
File: docs/adrs/004-external-service-adapters.md
Problem. The backend integrates with many external HTTP services (Gemini, GLM, Supabase, FCM, email, SMS, Cloudinary, 15+ data hub scrapers). The AI provider layer (app/services/ai/) already demonstrates a well-structured adapter pattern with AIProvider, GeminiProvider, GLMProvider, get_ai_provider(), and centralized tenacity retries - but other services implement their own error handling inconsistently, some with no retries. There is no circuit breaking: if Gemini is down, every request still tries it and waits for the full timeout cycle. HTTP client setup is duplicated.
Decision. Formalize the adapter pattern for all external service integrations. Each external service is wrapped behind an adapter interface that centralizes retry, circuit-breaking, timeout, and health tracking. Business code depends on adapter protocols, not on raw HTTP clients. The shared httpx.AsyncClient singletons in app/core/http.py (get_scraper_client, get_blog_client, get_general_client, get_supabase_auth_http_client) are a step in this direction.
Status. Accepted. The shared httpx clients exist and the AI provider factory is the reference implementation. Other services (email, SMS, FCM, scrapers) have not been migrated to the adapter pattern.
All four ADRs push in the same direction: decouple. Decouple domains from each other (ADR 001), services from persistence (ADR 002), primary operations from side effects (ADR 003), and business code from external services (ADR 004). The codebase has the seams in place (app/modules/, app/shared/, BaseRepository, SSEEventBus, shared httpx clients) but the migration is incremental.
- Features overview
- Ghar Core (marketplace)
- 360 Stays (bookings)
- 360 Flatmates
- Property Management
- 360 Virtual Tours
- 360 Data Hub
- MCP servers and widgets
- AI agent
- Blog and SEO
- Notifications
- Vastu analyzer