PSA/ARCHITECTURE_FIX.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

153 lines
12 KiB
Markdown

# Portal Layering Architecture Fix
## Problem
The `ContactPortalTab` component in `@alga-psa/clients` was directly importing portal-related actions from `@alga-psa/client-portal`, creating a horizontal (cross-layer) dependency between two domain layer packages:
```
❌ VIOLATION:
@alga-psa/clients (Layer 3 - Domain)
↓ imports directly from ↓
@alga-psa/client-portal (Layer 3 - Domain)
```
## Solution
Created a new infrastructure layer package `@alga-psa/portal-shared` (Layer 2) that:
1. Defines shared portal type definitions
2. Re-exports portal actions to break direct dependencies
3. Allows both domain packages to safely depend on it without circular dependencies
```
✅ FIXED:
@alga-psa/clients (Layer 3 - Domain)
↓ imports from ↓
@alga-psa/portal-shared (Layer 2 - Infrastructure) ← NEW PACKAGE
↓ re-exports from ↓
@alga-psa/client-portal (Layer 3 - Domain)
```
## Updated Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 5: Application │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ server (MSP app) │ EE modules │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────────────────┘
│ (depends on all layers below)
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 4: Presentation │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ @alga-psa/ui (UI components, hooks, utilities) │ │
│ │ (No dependencies on domain logic) │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 3: Domain Services │
│ (Each represents a business domain - should NOT depend on each other) │
│ ┌──────────────┬──────────────┬──────────────┬──────────────┬────────────┐ │
│ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/│ │
│ │ clients │ tickets │ client-portal│ projects │ billing │ │
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
│ ┌──────────────┬──────────────┬──────────────┬──────────────┬────────────┐ │
│ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/│ │
│ │ documents │ tags │notifications │ workflows │scheduling │ │
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
│ │
│ ✅ NO horizontal dependencies between packages (enforced via │
│ infrastructure layer re-exports when cross-cutting logic exists) │
└──────────────────────────┬──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 2: Infrastructure │
│ (Cross-cutting concerns and technical services) │
│ ┌──────────────┬──────────────┬──────────────┬──────────────┬────────────┐ │
│ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ │
│ │ auth │ users │ media │ email │ tenancy │ │
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
│ ┌──────────────┬──────────────┬──────────────┬──────────────┬────────────┐ │
│ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ │
│ │ validation │ integrations │ reference- │ portal-shared│ core │ │
│ │ │ │ data │ │ │ │
│ └──────────────┴──────────────┴──────────────┴──────────────┴────────────┘ │
│ │
│ NEW: @alga-psa/portal-shared - Re-exports portal functionality to break │
│ domain-level cross-dependencies │
└──────────────────────────┬──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYER 1: Foundation / Core │
│ (No dependencies on any other @alga-psa/* packages) │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ @alga-psa/ │ @alga-psa/ │ @alga-psa/ │ │
│ │ types │ core │ db │ │
│ │ │ │ │ │
│ │ - Interfaces │ - Utilities │ - Migrations │ │
│ │ - Type defs │ - Helpers │ - Schemas │ │
│ │ - Constants │ - Encryption │ - Knex setup │ │
│ └──────────────┴──────────────┴──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Files Changed
### New Package
- **Created:** `packages/portal-shared/` - New infrastructure layer package
- `package.json` - Package configuration
- `tsconfig.json` - TypeScript configuration
- `README.md` - Package documentation
- `src/types/index.ts` - Shared type definitions
- `src/actions/index.ts` - Actions index
- `src/actions/portalInvitationActions.ts` - Re-exported actions
- `src/index.ts` - Package entry point
### Updated Components
- **Modified:** `packages/clients/src/components/contacts/ContactPortalTab.tsx`
- Updated imports to use `@alga-psa/portal-shared` instead of `@alga-psa/client-portal`
- **Modified:** `packages/clients/src/components/contacts/ContactAvatarUpload.tsx`
- Updated imports to use `@alga-psa/portal-shared` instead of `@alga-psa/client-portal`
## Dependency Flow
### Before (Violation)
```typescript
// packages/clients/src/components/contacts/ContactPortalTab.tsx
import { sendPortalInvitation } from '@alga-psa/client-portal/actions'; // ❌ Direct cross-domain import
```
### After (Fixed)
```typescript
// packages/clients/src/components/contacts/ContactPortalTab.tsx
import { sendPortalInvitation } from '@alga-psa/portal-shared/actions'; // ✅ Via infrastructure layer
// packages/portal-shared/src/actions/portalInvitationActions.ts
export { sendPortalInvitation } from '@alga-psa/client-portal/actions'; // Re-export pattern
```
## Architecture Principles
This solution maintains the following architectural principles:
1. **No Circular Dependencies** - `@alga-psa/clients` does not directly depend on `@alga-psa/client-portal`
2. **Clean Layering** - Infrastructure layer (portal-shared) bridges domain-level concerns
3. **Separation of Concerns** - Each domain package owns its business logic
4. **Dependency Inversion** - Domain packages depend on abstractions (infrastructure re-exports)
5. **Facade Pattern** - Infrastructure layer provides a simplified interface to domain functionality
## Future Improvements
If portal functionality continues to grow, consider:
1. **Moving implementations to portal-shared** - Move actual `PortalInvitationService` and action implementations
2. **Creating domain event bus** - For more complex cross-domain communication patterns
3. **Using dependency injection** - For more sophisticated dependency management
4. **Creating orchestration layer** - Between application layer and domain packages for complex workflows