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

12 KiB

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)

// packages/clients/src/components/contacts/ContactPortalTab.tsx
import { sendPortalInvitation } from '@alga-psa/client-portal/actions'; // ❌ Direct cross-domain import

After (Fixed)

// 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