Skip to content

Dependency Graph Analysis & Decoupling Opportunities

Executive Summary

This document provides a comprehensive analysis of the SyRF monorepo dependency graph (both backend and frontend) and identifies 13 high-impact opportunities for simplification and decoupling. The analysis reveals a moderately coupled architecture with specific bottlenecks in both .NET services and Angular frontend.

Backend Key Findings: - ✅ No circular dependencies - Clean dependency hierarchy - ⚠️ SharedKernel is a god library - Contains 127 files with mixed concerns - ⚠️ WebHostConfig.Common creates unnecessary coupling - Pulls in Mongo, AppServices transitively - ✅ Services are reasonably decoupled - Most service-specific libraries affect only one service - ⚠️ API service has cross-boundary access - Directly references PM domain libraries

Frontend Key Findings: - ✅ Architecturally independent - No .NET dependencies, uses path aliases - ✅ Modern Angular 21 - Standalone components, ngrx state management - ⚠️ Large services - 1000+ line services (annotation-form, SignalR) - ⚠️ Large components - 800-900 line components (multiple) - ⚠️ State over-normalization - 30+ entity types may be excessive - ⚠️ Thin documentation - Basic src/services/web/README.md exists (added 2026); deeper architecture docs still missing

Impact Assessment: - Backend High Priority: 3 opportunities (SharedKernel refactoring, WebHostConfig splitting, API/PM boundary) - Backend Medium Priority: 3 opportunities (Message contract consolidation, AppServices scope, Quartz dependencies) - Backend Low Priority: 2 opportunities (Testing library, GUID serialization) - Frontend High Priority: 2 opportunities (Documentation, size guidelines) - Frontend Medium Priority: 2 opportunities (Refactor large services, extract form components) - Frontend Long-term: 1 opportunity (Review state normalization)


Progress Update (2026-04-24)

This document was originally authored on 2026-01-02. The qualitative findings and ranked opportunities below are still valid — the fundamental shape of backend coupling has not changed. However, a few things have shifted and are called out here so readers don't assume nothing has moved:

✅ Done since authoring

  • Frontend HTTP-wrapper deletion — Commit b2b4097d4 (refactor(web): delete redundant HTTP service wrapper classes) removed 11 thin wrapper services (account, admin-email, application, data-export, investigator, project, review, rob-ai, screening, search, study) plus their specs — 902 lines deleted. NSwag's useSingletonProvider made them redundant. Net effect: core service count dropped from 38 → 27 without losing capability.
  • GUID serialisation documentation (Opportunity 3.2) — docs/architecture/mongodb-serialization-architecture.md now exists and CLAUDE.md also carries a CSUUID / GuidRepresentation.CSharpLegacy section.
  • Frontend documentation entry point (Opportunity 9.3) — src/services/web/README.md now exists (still lightweight; deeper architecture docs remain an opportunity).
  • 1.2 Remove Mongo/AppServices from WebHostConfig — ✅ Landed in PR #2591 (a88025a70, 2d9d860ee, 1c7d91c4a). WebHostConfig.Common.csproj no longer references SyRF.Mongo.Common or SyRF.AppServices. Each feature lib now owns its own DI registry (MongoLamarRegistry, FileStorageRegistry) which hosts opt into via registry.IncludeRegistry<T>(). S3 types (S3Settings, IS3PostSigner, S3PostSigner, S3SignerBase) moved from SyRF.SharedKernel into SyRF.AppServices.FileServices. Net effect on Quartz: clean build no longer produces SyRF.Mongo.Common.dll or SyRF.AppServices.dll. See ADR-008.

🔄 In flight

  • annotation-form.service.ts refactor — Still 1,014 lines, but the Question Management v2 stack (PRs #2572 → #2575, split from #2461) is actively reshaping question-annotation flows. Any refactor of this service is best deferred until after QM v2 lands, to avoid conflict with in-flight work.

❌ Not yet actioned

Two high-priority backend items remain:

  • 1.1 Split SharedKernel — still monolithic (grew from 112 → 127 .cs files; PR #2591 moved 4 S3-related files out into SyRF.AppServices, a small net-negative but SharedKernel is still a god library).
  • 1.3 Enforce API/PM boundarySyRF.API.Endpoint.csproj still references PM.Core, PM.Messages, and PM.Mongo.Data directly (and as of PR #2591 also adds direct refs on Mongo.Common and AppServices, which were previously transitive via WebHostConfig).

Stale numbers in this doc

Sections below still reference project.service.ts (443 lines) and review.service.ts (344 lines) as large services — those files no longer exist (deleted in b2b4097d4). Treat the large-service list as annotation-form.service.ts (1,014) and signal-r.service.ts (762) only.

The Section 1.1 diagram and Section 1.4 impact radius below reflect the state prior to PR #2591 (WebHostConfig still shown pulling Mongo/AppServices). After that PR, the arrow from WebHostConfig down to Mongo/AppServices should be read as deleted, and Quartz's effective closure is only {SharedKernel, WebHostConfig.Common}.


1. Current Dependency Graph

1.1 Backend Service Dependency Overview

┌─────────────────────────────────────────────────────────────┐
│                        SERVICES                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┐     ┌──────────────────┐     ┌─────────┐    │
│  │   API    │     │  Project Mgmt    │     │ Quartz  │    │
│  │ (8080)   │     │     (8081)       │     │ (8082)  │    │
│  └────┬─────┘     └────────┬─────────┘     └────┬────┘    │
│       │                    │                    │          │
│       │                    │                    │          │
└───────┼────────────────────┼────────────────────┼──────────┘
        │                    │                    │
        ▼                    ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                        LIBRARIES                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│              ┌────────────────────┐                         │
│              │   SharedKernel     │ ◄──── CRITICAL          │
│              │  (127 files)       │       BOTTLENECK        │
│              └──────┬─────────────┘                         │
│                     │                                        │
│         ┌───────────┼───────────┬────────────┐             │
│         ▼           ▼           ▼            ▼             │
│  ┌──────────┐  ┌─────────┐  ┌────────┐  ┌────────────┐   │
│  │  Mongo   │  │   PM    │  │  API   │  │     PM     │   │
│  │ Common   │  │  Core   │  │Messages│  │  Messages  │   │
│  └────┬─────┘  └────┬────┘  └────────┘  └──────┬─────┘   │
│       │             │                            │          │
│       ▼             ▼                            │          │
│  ┌──────────┐  ┌──────────┐                    │          │
│  │   App    │  │    PM    │                     │          │
│  │ Services │  │Mongo.Data│◄────────────────────┘          │
│  └────┬─────┘  └──────────┘                                │
│       │                                                      │
│       ▼                                                      │
│  ┌──────────────────┐                                       │
│  │  WebHostConfig   │ ◄──── UNNECESSARY                     │
│  │     Common       │       COUPLING                        │
│  └──────────────────┘                                       │
│                                                              │
│  ┌────────────────────────────┐                            │
│  │  Independent Libraries     │                             │
│  ├────────────────────────────┤                            │
│  │ • S3FileSavedNotifier.Msg  │                            │
│  │ • Testing.Common           │                             │
│  └────────────────────────────┘                            │
└─────────────────────────────────────────────────────────────┘

Legend:
  ┌─────┐
  │ Box │  = Component
  └─────┘
     │     = Depends on (flows downward)

1.2 Dependency Statistics

Metric Count Notes
Total Services 6 (.NET) + 1 (frontend) + 2 (docs) API, PM, Quartz, S3-Notifier, Identity, Web, User-Guide, Docs
Total Libraries 9 core + 1 testing Excludes test projects
Circular Dependencies 0 ✅ Clean architecture
Critical Libraries 2 SharedKernel, Mongo.Common
Service-Specific Libs 5 API.Messages, PM.Core, PM.Messages, PM.Mongo.Data, S3.Messages

1.3 Frontend (Web Service) Dependency Overview

Technology Stack: - Framework: Angular 21 (modern, uses standalone components) - State Management: ngrx/store v21 - Components: 201 components - Services: 37 services (27 in core) — down from 48/38 at authoring; see Progress Update - Entity Types: 30+ entity slices in state

Architecture Pattern: Feature-based structure with centralized state management

Frontend (Angular 21)
├── app/
│   ├── core/                    ◄── Singleton services, state, interceptors
│   │   ├── services/ (27 svcs)  │   - API client (generated, via NSwag)
│   │   │                         │   - annotation-form (1014 lines ⚠️)
│   │   │                         │   - SignalR (762 lines)
│   │   │                         │   - route, layout, guards, diagnostics
│   │   ├── state/               │
│   │   │   ├── entities/        │   30+ entity types in ngrx store
│   │   │   │   ├── project/     │
│   │   │   │   ├── study/       │
│   │   │   │   ├── annotation/  │
│   │   │   │   └── ... (27 more)│
│   │   │   ├── ui/              │   UI state slices
│   │   │   ├── auth/            │   Authentication state
│   │   │   └── router/          │   Router state
│   │   ├── auth/                │   Auth0 integration
│   │   └── http-interceptors/   │   HTTP request handling
│   │
│   ├── shared/                  ◄── Reusable components & utilities
│   │   ├── annotation/          │   Shared annotation UI
│   │   ├── dialogs/             │   Dialog components
│   │   ├── form-controls/       │   Custom form controls
│   │   └── pipes/               │   Pipes & utilities
│   │
│   ├── project/                 ◄── Project feature module
│   ├── screening/               ◄── Screening feature module
│   ├── stage/                   ◄── Stage feature module
│   ├── studies/                 ◄── Studies feature module
│   └── ... (10+ feature areas)
└── Path Aliases (TypeScript):
    - @core/*    → src/app/core/*
    - @shared/*  → src/app/shared/*
    - @app/*     → src/app/*

✅ Positive Patterns: 1. Path Aliases: Uses TypeScript path mappings (@core, @shared) - prevents deep relative imports 2. Standalone Components: Modern Angular 21 pattern - better tree-shaking 3. Centralized State: ngrx store provides single source of truth 4. Feature Modules: Clear separation by feature area 5. No Backend Dependencies: Frontend is completely decoupled from .NET backend

⚠️ Concerns Identified: 1. Large Services: annotation-form.service.ts (1014 lines), signal-r.service.ts (762 lines) 2. Large Components: Several 800-900 line components (annotation-form, project-admin) 3. State Over-normalization: 30+ entity types may be excessive (consider aggregation) 4. Missing Documentation: No README or architecture docs in frontend codebase 5. Generated API Client: Tightly coupled to backend API shape (expected, but worth noting)

See Section 9: Frontend-Specific Opportunities for detailed decoupling recommendations.


1.4 Impact Radius by Library (Backend)

High Impact (affects 3+ services): - SharedKernelALL .NET services (API, PM, Quartz, S3-Notifier) - Changes trigger builds for all services - Contains 127 files with mixed concerns

Medium Impact (affects 2+ services): - Mongo.Common → API (direct post-PR #2591), PM (via PM.Mongo.Data) — Quartz dropped its transitive dep - AppServices → API (direct post-PR #2591), PM — Quartz dropped its transitive dep; now owns S3 types - WebHostConfig.Common → API, PM, Quartz - PM.Messages → PM, S3-Notifier

Low Impact (affects 1 service): - API.Messages → API only - PM.Core → PM only - PM.Mongo.Data → PM only (also used by API - see concern #3) - S3FileSavedNotifier.Messages → S3-Notifier only


2. Coupling Analysis

2.1 Critical Coupling Points

🔴 Critical Issue #1: SharedKernel God Library

Current State: - 127 files spanning multiple concerns - ALL .NET services depend on it - Mixed responsibilities: domain, infrastructure, utilities, config

File Breakdown by Concern:

SharedKernel (127 files)
├── Domain (30 files)
│   ├── BaseClasses/ (7 files) - AggregateRoot, Entity, Audit
│   ├── Enums/ (16 files) - Activity, Gender, ScreeningDecision, etc.
│   ├── ValueObjects/ (7 files) - Author, Journal, DateTimeRange
│   └── Interfaces/ (domain) - IAggregateRoot, IEntity, IDomainEvent
├── Infrastructure (40 files)
│   ├── ConfigModel/ (6 files) - AppSettings, ConnectionStrings
│   ├── DI/IoC (4 files) - SyrfRegistry, TaskRegistry, ProxyConvention
│   ├── Messaging (3 files) - MessageSender, EventManager
│   ├── AWS S3 (2 files) - S3PostSigner, S3SignerBase
│   ├── Observability (2 files) - Instrumentation, ActivityAndLog
│   └── Interfaces/ (infra) - IMessageBusService, IFileService, etc.
├── Utilities (25 files)
│   ├── Extensions - AutoMapper, Enumerable, etc.
│   ├── CSV - Csv.cs (CSV escaping)
│   ├── Helpers - MyUtils, SyrfHelpers, HttpHelpers
│   ├── Streams - BlockingStream, EnumerableStream
│   └── Patterns - Maybe, Result, Guard, Enumeration
└── Settings (5 files)
    ├── FeatureFlags.cs
    ├── S3Settings.cs
    └── SyrfSettings.cs

Why This Is Problematic: 1. Change Amplification: Any change triggers rebuilds of ALL services 2. Unclear Boundaries: Domain logic mixed with infrastructure utilities 3. Violation of SRP: Single library has 10+ different responsibilities 4. Difficult Testing: Hard to isolate concerns for unit testing 5. Cognitive Overhead: Developers must navigate 127 files to find domain concepts

Example Violations: - Csv.cs (utility) lives alongside AggregateRoot.cs (core domain) - S3PostSigner.cs (AWS infra) lives alongside Entity.cs (DDD pattern) - SyrfRegistry.cs (IoC container config) lives alongside ScreeningDecision.cs (enum)


🟢 Resolved Issue #2: WebHostConfig.Common Pulls Too Much (fixed in PR #2591)

Status: ✅ Resolved in PR #2591 (merged 2026-04-24). This section is kept for historical context.

Previous State (pre-PR #2591):

WebHostConfig.Common (shared web hosting setup)
├── Direct Dependencies:
│   ├── SharedKernel ✅ (reasonable)
│   ├── Mongo.Common ⚠️ (questionable - why does web config need DB?)
│   └── AppServices ⚠️ (questionable - why does web config need app logic?)
└── Used By:
    ├── API Service
    ├── PM Service
    └── Quartz Service ⚠️ (gets Mongo + AppServices transitively)

Current State (post-PR #2591):

WebHostConfig.Common (shared web hosting setup)
├── Direct Dependencies:
│   └── SharedKernel ✅
└── Used By:
    ├── API Service (adds direct refs on Mongo.Common, AppServices, PM libs via IncludeRegistry)
    ├── PM Service (adds direct refs on Mongo.Common, AppServices, PM libs via IncludeRegistry)
    └── Quartz Service (uses only WebHostConfig.Common — no Mongo / no AppServices)

How it was resolved: Each feature library now ships its own self-contained Lamar ServiceRegistry subclass (MongoLamarRegistry, FileStorageRegistry). Hosts that need a feature opt in with a single registry.IncludeRegistry<T>() call. S3 types (S3Settings, IS3PostSigner, S3PostSigner, S3SignerBase) moved from SyRF.SharedKernel into SyRF.AppServices.FileServices. See ADR-008 for the pattern.


🟡 Medium Issue #3: API Service Crosses PM Boundary

Current State:

API Service (SyRF.API.Endpoint) directly references:
├── PM.Core ⚠️ (domain logic)
├── PM.Messages ⚠️ (message contracts)
└── PM.Mongo.Data ⚠️ (data access layer)

Why This Is Problematic: 1. Bounded Context Violation: API service directly accesses PM domain internals 2. Tight Coupling: API can't evolve independently from PM 3. DDD Anti-pattern: One service directly manipulating another's aggregate roots 4. Migration Risk: Can't split PM into separate deployment without breaking API

Ideal State:

API Service should only reference:
├── PM.Messages ✅ (contracts only - via message bus)
└── API.Messages ✅ (its own contracts)

PM internals (Core, Mongo.Data) should be PRIVATE to PM service


2.2 Positive Coupling Patterns

What's Working Well:

  1. S3 Notifier is Perfectly Isolated
  2. Only depends on its own message library
  3. No shared infrastructure concerns
  4. Can deploy independently

  5. Web Service is Fully Decoupled

  6. Angular application, no .NET dependencies
  7. Independent build pipeline
  8. Changes don't affect backend services

  9. Message Libraries Follow Contracts Pattern

  10. PM.Messages, API.Messages, S3FileSavedNotifier.Messages
  11. Lightweight, interface-only libraries
  12. Enable async communication without coupling

  13. No Circular Dependencies

  14. Clean dependency hierarchy
  15. All dependencies flow one direction
  16. Easy to reason about build order

3. Decoupling Opportunities

Priority 1: High-Impact Refactorings

Opportunity 1.1: Split SharedKernel Into Focused Libraries

Current: 127-file monolithic library affecting all services

Proposed Structure:

SyRF.Core.Domain (15-20 files)
├── BaseClasses/
│   ├── AggregateRoot.cs
│   ├── Entity.cs
│   └── ValueObject.cs
├── Interfaces/
│   ├── IAggregateRoot.cs
│   └── IEntity.cs
└── Common domain patterns

SyRF.Core.Contracts (20-30 files)
├── Enums/ (all 16 enum files)
├── ValueObjects/ (shared VOs like Author, Journal)
└── Common DTOs

SyRF.Infrastructure.Common (15-20 files)
├── ConfigModel/
├── DI/ (SyrfRegistry, TaskRegistry)
├── Messaging/ (MessageSender, EventManager)
└── Observability/ (Instrumentation)

SyRF.Infrastructure.AWS (2 files)
├── S3PostSigner.cs
└── S3SignerBase.cs

SyRF.Utilities (20-25 files)
├── Extensions/
├── Helpers/
├── Csv.cs
├── Maybe.cs, Result.cs, Guard.cs
└── Stream utilities

Benefits: - ✅ Reduced Build Times: Changes to utilities don't trigger domain rebuilds - ✅ Clear Boundaries: Developers know where to find code - ✅ Independent Evolution: Can version infrastructure separately from domain - ✅ Better Testing: Easier to isolate concerns - ✅ Reduced Cognitive Load: Navigate 15-20 file libraries instead of 127

Migration Strategy: 1. Create new library projects (don't delete SharedKernel yet) 2. Move files to new homes (git mv to preserve history) 3. Update project references incrementally 4. Run full test suite after each library split 5. Delete SharedKernel only when empty

Estimated Effort: 2-3 days (with testing)

Risk: Medium (breaking change, requires coordination)


Opportunity 1.2: Remove Mongo/AppServices from WebHostConfig — ✅ DONE (PR #2591)

Status: Landed 2026-04-24. The rest of this subsection is kept for historical context / to document the approach actually taken.

Resolution: Implemented via Option A (below), plus the introduction of a co-located DI registry pattern so each feature library ships its own self-contained ServiceRegistry subclass. Hosts opt in via registry.IncludeRegistry<T>(). See ADR-008.

Previous Problem: WebHostConfig pulled in database + app services unnecessarily

Proposed Solution:

Option A: Remove Dependencies, Push Concerns Down

WebHostConfig.Common
- Remove: Mongo.Common dependency
- Remove: AppServices dependency
+ Keep: Core web hosting setup (logging, telemetry, MassTransit base config)

Each service configures its own:
- API Service → adds Mongo + AppServices registration
- PM Service → adds Mongo + AppServices registration
- Quartz Service → minimal setup (no DB needed)

Option B: Split into Specialized Libraries

SyRF.WebHostConfig.Core
├── Logging (Serilog)
├── Telemetry (OpenTelemetry, Sentry)
└── Health checks

SyRF.WebHostConfig.MassTransit
├── RabbitMQ setup
└── Message bus configuration

SyRF.WebHostConfig.Data (new, optional)
├── MongoDB registration
└── Repository setup

Benefits: - ✅ Quartz no longer pulls in Mongo + AppServices transitively - ✅ Each service declares exactly what it needs - ✅ Easier to understand what WebHostConfig actually does

Recommendation: Option A (simpler, less infrastructure)

Estimated Effort: 1 day

Risk: Low (internal refactoring, no public API changes)


Opportunity 1.3: Enforce API/PM Boundary

Current Problem: API service directly accesses PM domain + data layers

Proposed Solution: Contract-Based Integration via Message Bus

API Service Project References:
- Remove: PM.Core
- Remove: PM.Mongo.Data
+ Keep: PM.Messages (message contracts only)
+ Keep: API.Messages (own contracts)

Communication Pattern:
API Service                         PM Service
    │                                   │
    ├─ Publish: CreateProjectCommand ──>│
    │                                   ├─ Handle command
    │                                   ├─ Save to Mongo
    │<── Publish: ProjectCreatedEvent ──┤
    │                                   │
    ├─ Query: GetProjectQuery ─────────>│
    │<── Response: ProjectDto ───────────┤

Migration Path: 1. Identify cross-boundary calls in API service 2. Define message contracts for each operation (commands, queries, events) 3. Implement handlers in PM service 4. Replace direct calls with message bus publish/subscribe 5. Remove project references to PM.Core and PM.Mongo.Data

Benefits: - ✅ True Bounded Contexts: PM service owns its domain completely - ✅ Independent Deployment: Can scale/deploy services separately - ✅ Async by Default: Natural async processing (better performance) - ✅ Easier Testing: Mock message bus instead of repositories - ✅ Future-Proof: Can split into microservices later

Trade-offs: - ⚠️ More message contracts to maintain - ⚠️ Requires RabbitMQ infrastructure (already in place) - ⚠️ Learning curve for message-based patterns

Estimated Effort: 3-5 days (depends on # of cross-boundary calls)

Risk: Medium (architectural change, requires careful testing)


Priority 2: Medium-Impact Improvements

Opportunity 2.1: Consolidate Message Libraries

Current State: 3 separate message libraries

API.Messages (used by API only)
PM.Messages (used by PM + Quartz)
S3FileSavedNotifier.Messages (used by S3-Notifier only)

Observation: Only PM.Messages is actually shared (used by Quartz for job processing)

Proposed Optimization:

Option A: Keep Current Structure (if planning microservices) - Each service owns its message contracts - Shared contracts extracted to common libraries only when needed - ✅ Aligns with microservices future - ⚠️ More projects to maintain

Option B: Create SyRF.Contracts.Messages (if staying monolithic)

SyRF.Contracts.Messages
├── PM/ (ProjectManagement events/commands)
├── API/ (API events/commands)
└── S3/ (S3 events)
- ✅ Single location for all contracts - ✅ Easier cross-service communication - ⚠️ Creates coupling if splitting services later

Recommendation: Option A (current structure is fine for microservices path)

Estimated Effort: N/A if keeping current, 4 hours if consolidating

Risk: Low


Opportunity 2.2: Clarify AppServices Scope

Current State: AppServices contains file upload services (S3, local)

Questions: - Why does this live in a separate library vs SharedKernel or service-specific? - Is this truly shared across multiple services or just historical?

Investigation Needed:

# Check actual usage
grep -r "SyRF.AppServices" src/services/*/

Proposed Actions: 1. If used by 2+ services → Keep as shared library ✅ 2. If used by 1 service → Move into that service's codebase 3. If file services are infrastructure → Consider moving to Infrastructure.AWS

Benefits: Clearer ownership and reduced library count

Estimated Effort: 2-4 hours investigation + potential move

Risk: Low


Opportunity 2.3: Quartz Dependency Minimization

Current State: Quartz depends on WebHostConfig → pulls in Mongo + AppServices

After Fix #1.2 (removing Mongo/AppServices from WebHostConfig): - Quartz → WebHostConfig (lightweight) - Quartz → PM.Messages (for job contracts)

Further Optimization: Extract Job Contracts

SyRF.Jobs.Contracts (new library)
├── IBackgroundJob.cs
├── JobContext.cs
└── Job-specific message contracts (if any)

Quartz Service:
- Depends on: Jobs.Contracts (instead of PM.Messages)

Benefits: Quartz completely decoupled from PM internals

Trade-off: Another library to maintain

Recommendation: Wait until after WebHostConfig refactoring to assess need

Estimated Effort: 2-3 hours (if needed)

Risk: Low


Priority 3: Low-Impact Optimizations

Opportunity 3.1: Extract Testing Library Concerns

Current State: SyRF.Testing.Common exists but isn't referenced in dependency map

Investigation Needed: - What's in this library? - Is it used by test projects? - Should it be split into test utilities vs test data?

Proposed Review: Document testing infrastructure and ensure test dependencies don't leak into production code

Estimated Effort: 1-2 hours audit

Risk: Very Low


Opportunity 3.2: GUID Serialization Centralization

Context: MongoDB uses GuidRepresentation.CSharpLegacy (CSUUID format)

Current State: Configuration in MongoUtils.cs, documented in CLAUDE.md

Opportunity: Create SyRF.MongoDB.Serialization library to centralize: - GUID serialization config - BSON serializers - MongoDB-specific type converters

Benefits: - ✅ Single source of truth for MongoDB serialization - ✅ Easier to test serialization behavior - ✅ Can reuse in test projects

Trade-off: Another library (but focused concern)

Estimated Effort: 3-4 hours

Risk: Very Low (refactoring existing code)


4. Dependency Flow Visualization

4.1 Before Refactoring (Current State)

┌─────────────────────────────────────────────────┐
│                  Services                        │
└─────────────────────────────────────────────────┘
         │              │             │
         ▼              ▼             ▼
    ┌────────┐    ┌─────────┐   ┌─────────┐
    │  API   │    │   PM    │   │ Quartz  │
    └────┬───┘    └────┬────┘   └────┬────┘
         │             │              │
         └─────────────┼──────────────┘
              ┌─────────────────┐
              │  SharedKernel   │ ◄── GOD LIBRARY
              │   (127 files)   │     (everything depends on this)
              └─────────────────┘
         ┌─────────────┼─────────────┬──────────────┐
         ▼             ▼             ▼              ▼
   ┌─────────┐  ┌──────────┐  ┌──────────┐  ┌───────────┐
   │  Mongo  │  │   App    │  │ WebHost  │  │    PM     │
   │ Common  │  │ Services │  │  Config  │  │   Core    │
   └─────────┘  └──────────┘  └────┬─────┘  └───────────┘
                    ┌───────────────┴────────────┐
                    ▼                            ▼
              ┌──────────┐                ┌──────────┐
              │   Mongo  │                │   App    │
              │  Common  │                │ Services │
              └──────────┘                └──────────┘
                    ▲                            ▲
                    └── TRANSITIVE COUPLING ─────┘
                        (pulled in via WebHostConfig)

Problems Visualized: 1. 🔴 Single Point of Failure: SharedKernel affects everything 2. 🟡 Transitive Bloat: WebHostConfig pulls Mongo + AppServices 3. 🟡 Cross-Boundary Leakage: API → PM.Core/PM.Mongo.Data


4.2 After Refactoring (Proposed State)

┌──────────────────────────────────────────────────────┐
│                     Services                          │
└──────────────────────────────────────────────────────┘
    │                    │                    │
    ▼                    ▼                    ▼
┌────────┐         ┌──────────┐         ┌─────────┐
│  API   │◄───────►│   PM     │         │ Quartz  │
└────┬───┘ Message └────┬─────┘         └────┬────┘
     │     Bus           │                    │
     │                   │                    │
     ▼                   ▼                    ▼
┌──────────────────────────────────────────────────────┐
│             Core Libraries (Focused)                  │
├──────────────────────────────────────────────────────┤
│  ┌────────────┐  ┌────────────┐  ┌──────────────┐  │
│  │   Core     │  │   Core     │  │  WebHost     │  │
│  │  Domain    │  │ Contracts  │  │     Core     │  │
│  │ (20 files) │  │ (30 files) │  │ (lightweight)│  │
│  └────────────┘  └────────────┘  └──────────────┘  │
│                                                       │
│  ┌────────────┐  ┌────────────┐  ┌──────────────┐  │
│  │   Infra    │  │   Infra    │  │  Utilities   │  │
│  │   Common   │  │    AWS     │  │  (25 files)  │  │
│  │ (20 files) │  │  (2 files) │  └──────────────┘  │
│  └────────────┘  └────────────┘                     │
│                                                       │
│  ┌──────────────────────────────────────────────┐   │
│  │   Domain-Specific Libraries (Private)         │   │
│  ├──────────────────────────────────────────────┤   │
│  │  PM.Core  │  PM.Messages  │  PM.Mongo.Data  │   │
│  │  API.Msgs │  S3.Messages  │                  │   │
│  └──────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────┘

Improvements Visualized: 1. ✅ Focused Libraries: Each 15-30 files instead of 127 2. ✅ Clear Layers: Domain → Contracts → Infrastructure → Utilities 3. ✅ Service Isolation: PM internals not leaked to API 4. ✅ Reduced Blast Radius: Changes to utilities don't affect domain


5. Implementation Roadmap

Phase 1: Quick Wins (1-2 days)

Goal: Reduce coupling without major restructuring

  1. Remove Mongo/AppServices from WebHostConfig (Opportunity 1.2)
  2. Estimated: 1 day
  3. Impact: Quartz becomes lighter
  4. Risk: Low

  5. Audit AppServices Usage (Opportunity 2.2)

  6. Estimated: 4 hours
  7. Impact: Understand if consolidation possible
  8. Risk: Very Low

  9. Document Testing Library (Opportunity 3.1)

  10. Estimated: 2 hours
  11. Impact: Clarity on test infrastructure
  12. Risk: Very Low

Phase 2: Architectural Improvements (1 week)

Goal: Enforce service boundaries

  1. Split SharedKernel (Opportunity 1.1)
  2. Estimated: 2-3 days
  3. Impact: Massive - reduces build times, improves clarity
  4. Risk: Medium (requires testing)
  5. Prerequisite: None, can start immediately

  6. Enforce API/PM Boundary (Opportunity 1.3)

  7. Estimated: 3-5 days
  8. Impact: True microservices readiness
  9. Risk: Medium (architectural change)
  10. Prerequisite: Complete after SharedKernel split

Phase 3: Optional Optimizations (Ongoing)

Goal: Fine-tune based on learnings

  1. 🔄 Quartz Dependency Minimization (Opportunity 2.3)
  2. Estimated: 2-3 hours
  3. Impact: Complete Quartz isolation
  4. Risk: Low
  5. Prerequisite: After WebHostConfig refactoring

  6. 🔄 GUID Serialization Library (Opportunity 3.2)

  7. Estimated: 3-4 hours
  8. Impact: MongoDB serialization centralization
  9. Risk: Very Low

  10. 🔄 Message Library Review (Opportunity 2.1)

  11. Estimated: 4 hours (if consolidating)
  12. Impact: Depends on microservices strategy
  13. Risk: Low

6. Success Metrics

6.1 Quantitative Metrics

Before Refactoring: | Metric | Current Value | |--------|---------------| | Largest library size | 127 files (SharedKernel) | | Services affected by SharedKernel change | 4 (100% of .NET services) | | Services affected by Mongo.Common change | 2 (API, PM) | | Cross-boundary references | 3 (API → PM.Core, PM.Messages, PM.Mongo.Data) | | Transitive dependencies in Quartz | 5 (via WebHostConfig) |

After Refactoring (Target): | Metric | Target Value | Improvement | |--------|--------------|-------------| | Largest library size | 30 files (Core.Contracts) | 74% reduction | | Services affected by Core.Domain change | 2-3 (only services using domain) | 25-50% reduction | | Services affected by Utilities change | 0-1 (isolated concern) | 75-100% reduction | | Cross-boundary references | 0 (API → PM.Messages only) | 100% elimination | | Transitive dependencies in Quartz | 1 (WebHostConfig.Core only) | 80% reduction |


6.2 Qualitative Metrics

Developer Experience: - ✅ Faster builds (smaller dependency graphs) - ✅ Easier code navigation (focused libraries) - ✅ Clear ownership (bounded contexts respected) - ✅ Better testability (isolated concerns)

Architectural Health: - ✅ Services can deploy independently - ✅ Domain logic isolated from infrastructure - ✅ Clear separation of concerns - ✅ Future-proof for microservices


7. Risk Assessment

7.1 Refactoring Risks

Risk Likelihood Impact Mitigation
Breaking changes during SharedKernel split Medium High Incremental migration, comprehensive test suite
Message bus overhead in API/PM boundary Low Medium Monitor performance, implement caching if needed
Regression bugs from dependency changes Medium Medium Full regression testing, staged rollout
Developer confusion during transition Medium Low Clear documentation, team training
Build pipeline changes Low Low Update CI/CD path filters, test thoroughly

7.2 Risk Mitigation Strategies

  1. Incremental Migration:
  2. Don't delete old libraries until new ones proven
  3. Use git branching for each major refactoring
  4. Merge frequently to avoid drift

  5. Comprehensive Testing:

  6. Run full test suite after each library split
  7. Add integration tests for message-based communication
  8. Monitor staging environment closely

  9. Documentation:

  10. Update CLAUDE.md with new structure
  11. Create ADR for each major decision
  12. Document migration path for developers

  13. Rollback Plan:

  14. Git history preserves old structure
  15. Can revert individual commits if issues arise
  16. Staged deployment allows testing in isolation

8. Conclusion

The SyRF monorepo has a moderately healthy dependency architecture with no circular dependencies and reasonable service isolation. However, there are 13 high-impact opportunities spanning both backend and frontend that would significantly improve the codebase.

Critical Improvements - Backend (Must Do):

  1. Split SharedKernel into 5 focused libraries
  2. Reduces blast radius of changes
  3. Improves build times
  4. Clarifies architectural layers

  5. Remove Mongo/AppServices from WebHostConfig

  6. Eliminates transitive dependency bloat
  7. Each service declares explicit needs

  8. Enforce API/PM boundary via message bus

  9. Enables true microservices architecture
  10. Prevents domain logic leakage
  11. Improves testability

Critical Improvements - Frontend (Quick Wins):

  1. Add Frontend Architecture Documentation
  2. Improves onboarding and consistency
  3. 1-2 days effort, very low risk

  4. Establish Component/Service Size Guidelines

  5. Forces decomposition of complex code
  6. 1 hour effort, very low risk

Week 1 - Quick Wins: 1. Frontend documentation (Opportunity 9.3) - 1-2 days 2. Frontend size guidelines (Opportunity 9.5) - 1 hour 3. Backend: Remove Mongo/AppServices from WebHostConfig (Opportunity 1.2) - 1 day

Week 2-3 - Backend Architectural Improvements: 4. Split SharedKernel (Opportunity 1.1) - 2-3 days 5. Enforce API/PM boundary (Opportunity 1.3) - 3-5 days

Ongoing - Frontend Refactoring: 6. Refactor large services (Opportunity 9.1) - 2-3 days per service 7. Extract reusable form components (Opportunity 9.4) - 3-4 days

Long-term - Optimization: 8. Review frontend state normalization (Opportunity 9.2) - 1 week

Long-Term Vision:

With these refactorings in place, SyRF will have:

Backend: - ✅ Clear architectural boundaries (domain, contracts, infrastructure, utilities) - ✅ Independent service deployment (API, PM, Quartz can scale separately) - ✅ Faster build times (smaller dependency graphs) - ✅ Better developer experience (focused libraries, clear ownership) - ✅ Microservices-ready (message-based integration, bounded contexts)

Frontend: - ✅ Well-documented architecture (easier onboarding) - ✅ Smaller, focused components and services (easier maintenance) - ✅ Reusable UI building blocks (consistent user experience) - ✅ Optimized state management (better performance) - ✅ Enforced code quality (size guidelines prevent bloat)


9. Frontend-Specific Opportunities

While the frontend is architecturally independent from the backend (no .NET dependencies), there are internal coupling and complexity issues worth addressing.

Opportunity 9.1: Refactor Large Services

Problem: Several services exceed 700-1000 lines

Largest Offenders (as of 2026-04-18):

Service Lines Concern
annotation-form.service.ts 1014 Handles form state, validation, API calls, business logic
signal-r.service.ts 762 WebSocket connection, reconnection logic, event handling

Earlier drafts of this doc also listed project.service.ts (443 lines) and review.service.ts (344 lines). Both were deleted in commit b2b4097d4 — NSwag's useSingletonProvider made those hand-written wrappers redundant.

Proposed Refactoring:

Example: annotation-form.service.ts (1014 lines)

Current (monolithic):
annotation-form.service.ts
├── Form state management
├── Validation logic
├── API client calls
├── Business rules
├── UI state synchronization
└── Event handling

Proposed (focused services):
annotation-form-state.service.ts (200 lines)
├── Form state management only
└── RxJS state stream

annotation-form-validator.service.ts (150 lines)
├── Validation rules
└── Custom validators

annotation-form-api.service.ts (100 lines)
├── API calls only
└── HTTP operations

annotation-form-business.service.ts (200 lines)
├── Business logic
└── Orchestration

annotation-form.facade.ts (150 lines)
├── Facade pattern
└── Coordinates all services above

Benefits: - ✅ Easier testing (smaller units) - ✅ Better code navigation - ✅ Single Responsibility Principle - ✅ Reusability of validation/API logic

Estimated Effort: 2-3 days per large service

Risk: Medium (requires careful testing of form workflows)

⚠️ In-flight work (2026-04-18): annotation-form.service.ts is a primary target of the Question Management v2 stack (PRs #2572 → #2575, split from #2461). Any decomposition of this service should be scheduled after QM v2 lands to avoid rebase pain. The line count here (1,014) reflects the state today; QM v2 will reshape it significantly.


Opportunity 9.2: Review Entity State Normalization

Problem: 30+ entity types in ngrx store may be over-normalized

Current Entity Types (from entity.reducer.ts): - screenings, memberships, projects, projectDetails, projectSecureDetails - searches, stages, stageAnnotationStats, stageUsages - studies, studyDetails, annotations, annotationQuestions - outcomeData, sessions, investigators, investigatorSecureDetails - projectGroups, projectUsages, searchImportJobs, riskOfBiasJobs - joinRequests, invitations, invitationSecureDetails - adminInvestigators, emailTemplates, projectPermissionSet - stagePermissionSet, reviewerScreeningStats, reviewerAnnotationStats - bulkStudyUpdateJobs, dataExportJobs, dataExportRequests - (30+ types total)

Questions to Investigate: 1. Are project and projectDetails actually needed separately? 2. Can *Stats entities be aggregated into parent entities? 3. Are *SecureDetails truly separate concerns or just ACL filtered views? 4. Do all job types need separate entity slices?

Proposed Investigation:

# Analyze entity usage patterns
1. Which entities are ALWAYS loaded together?
2. Which entities have 1:1 relationships?
3. Which entities are rarely accessed independently?

Potential Consolidations:

Before:
- project (30 entities)
- projectDetails (separate entity)
- projectSecureDetails (separate entity)
- projectScreeningStats (separate entity)
- projectUsages (separate entity)

After (option):
- project (enhanced entity with details/stats/usage as properties)
  - OR keep projectDetails separate if lazy-loaded
  - OR keep SecureDetails separate for security boundary

Benefits: - ✅ Simpler selectors (fewer joins) - ✅ Fewer reducer files to maintain - ✅ Better performance (fewer state slices to check) - ⚠️ Trade-off: Slightly larger entity objects

Estimated Effort: 1 week (requires careful analysis + migration)

Risk: High (state refactoring affects entire app)


Opportunity 9.3: Add Frontend Architecture Documentation

Problem: No README or architecture docs in frontend codebase

Proposed Documentation (src/services/web/README.md): - Application architecture overview - State management patterns (ngrx usage) - Routing strategy - Component hierarchy and conventions - Service layer patterns - Testing strategy - Build and deployment process

Proposed Documentation (src/services/web/ARCHITECTURE.md): - Feature module breakdown - State slice responsibility map - Service dependency graph - Component communication patterns - Performance optimization strategies

Benefits: - ✅ Faster onboarding for new developers - ✅ Consistent patterns across features - ✅ Design decision documentation

Estimated Effort: 1-2 days

Risk: Very Low


Opportunity 9.4: Extract Reusable Form Components

Observation: Large components (800-900 lines) suggest missing abstractions

Largest Components: | Component | Lines | Likely Issue | |-----------|-------|--------------| | annotation-form.component.ts | 943 | Complex form logic, validation, submission | | create-question.component.ts | 875 | Question designer with multiple question types | | project-index.component.ts | 859 | Project list with filtering, sorting, pagination | | project-members.component.ts | 842 | Member management, invitations, permissions |

Proposed Pattern: Composition over Inheritance

Example: annotation-form.component.ts (943 lines)

Current (monolithic):
annotation-form.component.ts
├── Form rendering
├── Validation display
├── Field type switching
├── Submit logic
├── Error handling
└── State management

Proposed (composed):
annotation-form.component.ts (200 lines)
├── <app-form-field> components (extracted)
├── <app-validation-messages> (extracted)
├── <app-form-actions> (extracted)
└── Orchestration only

Extracted components:
- form-field-text.component.ts
- form-field-select.component.ts
- form-field-multiselect.component.ts
- validation-messages.component.ts
- form-actions.component.ts (Save/Cancel buttons)

Benefits: - ✅ Reusable form building blocks - ✅ Easier testing of individual field types - ✅ Consistent validation UI across app - ✅ Better code organization

Estimated Effort: 3-4 days (extract + refactor)

Risk: Medium (requires thorough UI testing)


Opportunity 9.5: Establish Component Size Guidelines

Proposed Guidelines: - Components: Max 300 lines (prefer 100-200) - Services: Max 400 lines (prefer 200-300) - If exceeding limits: Extract to: - Child components (for UI) - Facade services (for orchestration) - Utility functions (for pure logic)

Enforcement: Add ESLint rule for max file size

// .eslintrc.json
"rules": {
  "max-lines": ["warn", {
    "max": 300,
    "skipBlankLines": true,
    "skipComments": true
  }]
}

Benefits: - ✅ Forces decomposition of complex components - ✅ Improves code readability - ✅ Easier code reviews

Estimated Effort: 1 hour (add rule), ongoing refactoring as needed

Risk: Very Low


Frontend Refactoring Priority

Priority 1 (High Impact): 1. Document Frontend Architecture (Opportunity 9.3) - 1-2 days, very low risk 2. Establish Size Guidelines (Opportunity 9.5) - 1 hour, very low risk

Priority 2 (Medium Impact): 3. Refactor Largest Service (Opportunity 9.1) - Start with annotation-form.service.ts 4. Extract Reusable Form Components (Opportunity 9.4) - Reduces duplication

Priority 3 (Long-term): 5. Review Entity Normalization (Opportunity 9.2) - Requires careful analysis


Appendix A: Detailed File Analysis

SharedKernel Files by Category

Domain (30 files): - BaseClasses/ (7): AggregateRoot, Entity, ValueObject, Audit, etc. - Enums/ (16): Activity, AnnotationStatus, Gender, ScreeningDecision, etc. - ValueObjects/ (7): Author, Journal, DateTimeRange, Keyword, etc.

Infrastructure (40 files): - ConfigModel/ (6): AppSettingsConfig, ConnectionStrings, MongoConnectionSettings - DI/IoC (4): SyrfRegistry, TaskRegistry, AddProxyConvention - Messaging (3): MessageSender, EventManager, IMessageBusService - AWS (2): S3PostSigner, S3SignerBase - Observability (2): Instrumentation, ActivityAndLogDispatchProxy - Interfaces/ (20+): Various infrastructure interfaces

Utilities (25 files): - Extensions: AutoMapperExtensions, EnumerableExtensions - Helpers: MyUtils, SyrfHelpers, HttpHelpers, Guard - CSV: Csv.cs - Patterns: Maybe, Result, Enumeration - Streams: BlockingStream, EnumerableStream - Others: HeaderDictionaryBuilder, ReturnFile, Sort, etc.

Settings (5 files): - FeatureFlags, S3Settings, SyrfSettings

Other (12 files): - ApplicationRole, Constants, GitVersion, etc.


Appendix B: References

  • Dependency Map (Source): /docs/architecture/dependency-map.yaml
  • Dependency Map (Documentation): /docs/architecture/dependency-map.md
  • MongoDB Reference: /docs/architecture/mongodb-reference.md
  • GitOps Architecture: /docs/architecture/gitops-architecture.md
  • ADR-003: Cluster Architecture

Document Status: ✅ Approved Last Updated: 2026-04-18 Next Review: After Phase 1 completion