Platform Architecture
The blueprint of PKahfi Platform. A modern, distributed SaaS architecture designed for scalability, multi-tenancy, and rapid delivery.
We follow the Modular Monolith pattern within our Monorepo. While all applications can be deployed independently, the core business logic is encapsulated into Bounded Contexts and Shared Packages.
Isolation
Strict boundaries between modules using internal packages.
Shared Kernel
Common logic is centralized in the Shared Kernel layer (@repo/shared-*).
Efficiency
Single deployment unit for reduced dev-ops overhead.
apps/
- • dashboard - Central Management
- • pkahfi-docs - Platform Documentation
- • weddingos - Digital Invitation SaaS
- • design-system - UI Documentation
packages/
- • shared/ui - Design System Library
- • shared/config - Tailwind, TS, ESLint
- • core/domain - Business Entities
- • bc/billing - Bounded Context: Billing
Our architecture uses a Shared Database approach with strong isolation at the database level using Postgres RLS. This ensures that a tenant can never access another tenant's data, even if the application layer is compromised.
Domain
Pure business logic, entities, and value objects.
Application
Use cases, services, and DTOs.
Infrastructure
Frameworks, DB implementation, and external APIs.
Golden Rule: Unidirectional Flow
- Domain is a LeafThe Domain layer must NEVER depend on any other layer or external package.
- No Circular DepsPackage A depends on B? B cannot depend on A. Period.
- Shared Kernel ExceptionPackages under @repo/shared-* can be imported by any layer as utilities.
- Feature IsolationFeature modules in apps should not import from each other directly (use Shared).
Events enable modules to communicate without being tightly coupled. We distinguish between Domain Events (internal) and Integration Events (external).
In-Process (Sync)
// Aggregate triggers event
this.addDomainEvent(new UserRegistered(id));
// Dispatched during Save()
await DomainEvents.dispatch(entity);
Used for immediate side effects within the same transaction/bounded context.
Asynchronous (External)
Used for cross-module communication and background tasks. We prioritize Inngest to eliminate Redis overhead during the bootstrapping phase. BullMQ is reserved as a future scaling path.
During the Heavy Development phase, we minimize fixed infrastructure costs (No self-managed Redis, No heavy K8s). We leverage:
Using Inngest or simple Postgres-based pooling for reliability without Redis.
Minimizing cold starts and cold server costs using Vercel Edge/Serverless.