Skip to content

Cesivi Server - Architecture & Design Decisions

HomeDocumentationReference → Architecture

This document explains the architectural design, key decisions, and patterns used in the Cesivi Server.

System Overview

The mock server implements SharePoint Server Subscription Edition using a 4-layer architecture:

Layer 4: Endpoints
         REST API → SOAP Services → CSOM → PnP PowerShell
                        ↓
Layer 3: Initialization & Business Logic
         MockData Seeding → CSOM Processor → Services
                        ↓
Layer 2: Persistence (IStorageService)
         FileSystemStorageService | ADMockStorageService
                        ↓
Layer 1: Domain Models
         SPWeb, SPList, SPListItem, SPField, SPFile, etc.

Key Architectural Decision: REST Wraps SOAP

Principle: All endpoints delegate to a single source of truth

All REST endpoints ultimately call the same SOAP services: - REST → SharePointApiController → _listsService.GetListsAsync() - SOAP → ListsController → _listsService.GetListsAsync() - CSOM → CsomProcessor → _listsService.GetListsAsync()

Benefits: - ✅ 100% consistency between REST and SOAP - ✅ Zero duplicate business logic - ✅ Changes propagate to all protocols - ✅ Easier testing (test service once, use everywhere)

Layer 1: Domain Models (Type-Safe Design)

Philosophy: Strongly-Typed, No Dictionaries

// ✅ Good: Type-safe, compile-time safety
public class SPList
{
    public string Title { get; set; }
    public List<SPListItem> Items { get; set; }
    public List<SPField> Fields { get; set; }
}

// ❌ Avoided: Runtime-unsafe
public class SPList
{
    public Dictionary<string, object> Properties { get; set; }
}

Key Characteristics

  1. No Dictionary - Compile-time safety
  2. Nullable Reference Types - string? prevents null exceptions
  3. Enums for Constants - ListBaseType.DocumentLibrary
  4. JSON Property Ordering - [JsonProperty(Order = X)] for CSOM
  5. Comprehensive Properties - Match real SharePoint objects

Model Hierarchy

SPSecurable (permissions)
  ├─ SPWeb (site)
  ├─ SPList (list/library)
  ├─ SPListItem (item)
  ├─ SPField (column)
  └─ SPContentType (content type)

SPPrincipal (security)
  ├─ SPUser
  ├─ SPGroup
  └─ SPRole

SPFile (documents)
  └─ SPFolder

Layer 2: Persistence (IStorageService)

Philosophy: Async-First, Dependency Injection

public interface IStorageService
{
    // All methods are async
    Task<SPWeb> LoadWebAsync(string webId, CancellationToken ct = default);
    Task SaveWebAsync(SPWeb web, CancellationToken ct = default);
    Task<List<SPList>> LoadAllListsAsync(string webId, CancellationToken ct = default);
}

Implementations (7 Providers)

FileSystemStorageService: - Stores data as JSON on disk - ReaderWriterLockSlim for concurrent reads - Atomic writes with backup - SQLite sidecar index for query push-down - MoveFolderAsync / CopyFolderAsync with recursive operations

InMemoryStorageService: - ConcurrentDictionary-based in-memory storage - Full feature parity with FileSystem - Ideal for testing and ephemeral scenarios

Database Providers (SQLite, LiteDB, SQL Server, PostgreSQL, MySQL): - Full SQL/LINQ query push-down (WHERE, ORDER BY, LIMIT, OFFSET) - Transactional consistency - Configurable via IStorageService DI

ADMockStorageService: - Users/groups in Active Directory mock - LDAP-compatible responses

Key Principles

  1. No Blocking Calls - All I/O is async/await
  2. Concurrent Reads - ReaderWriterLockSlim optimization
  3. Atomic Writes - Entire object or nothing
  4. Automatic Backup - Recovery if corruption
  5. Testable - Interface-based, swappable implementations

File System Structure

@MockData/
  Farm/
    settings.json
    GlobalTermStore/settings.json
    SearchIndex/index.json
    Services/
  WebApplications/{webapp}/
    settings.json
    SiteCollections/{sc}/
      settings.json
      Users/{userId}.json
      Groups/{groupId}.json
      TermStore/settings.json
      RecycleBin/{itemId}.json
      RootWeb/
        settings.json
        ContentTypes/{ctId}.json
        Fields/{fieldId}.json
        Roles/{roleId}.json
        Lists/{listName}/
          settings.json
          Fields/{fieldId}.json
          Views/{viewId}.json
          Items/{itemId}.json
          Attachments/{itemId}/{filename}
        Libraries/{libName}/
          settings.json
          {filename}
          {filename}.meta.json
          .versions/{filename}/{version}

Key Patterns

File Storage Pattern

  • Binary files stored directly in filesystem
  • Companion .meta.json for metadata
  • Versions: .versions/{filename}/{versionLabel}
  • Attachments: Lists/{list}/Attachments/{itemId}/

Permission Inheritance

  • Hierarchical: File → Folder → List → Web → Site
  • HasUniqueRoleAssignments flag breaks inheritance
  • GetEffectivePermissions() walks parent chain

Search Indexing

  • Inverted index: Term → [DocumentId, Position, Frequency]
  • TF-IDF scoring for relevance ranking
  • Incremental indexing on Add/Update/Delete operations
  • Storage: @MockData/Farm/SearchIndex/index.json

Content Types

  • Hierarchical inheritance: List CT → Web CT → Site CT → Built-in CT
  • Stored at web level: ContentTypes/{ctId}.json
  • List associations: reference or copy web CT

Data Model Examples

Web (SPWeb)

{
  "Id": "guid",
  "Title": "Team Site",
  "Url": "http://server/sites/site",
  "Description": "...",
  "Created": "2025-01-01T00:00:00",
  "HasUniqueRoleAssignments": false
}

List (SPList)

{
  "Id": "guid",
  "Title": "Documents",
  "BaseType": 1,
  "BaseTemplate": 101,
  "ItemCount": 0,
  "EnableVersioning": true,
  "ContentTypesEnabled": true
}

File Metadata

{
  "Name": "document.pdf",
  "ServerRelativeUrl": "/Documents/document.pdf",
  "TimeCreated": "2025-01-01T00:00:00",
  "TimeLastModified": "2025-01-02T00:00:00",
  "Length": 1024,
  "CheckOutType": 0,
  "CheckedOutByUserId": null,
  "MajorVersion": 1,
  "MinorVersion": 0
}

Critical Architectural Decisions

Decision 1: CSOM Coverage Achieved ~98%

History: Early investigations (S2.641) concluded CSOM coverage was limited to ~70.3% due to undocumented protocol. Subsequent development (MASTERPLAN v23-v37, 2026-02-07 through 2026-02-12) proved this wrong — systematic CSOM implementation achieved ~98% coverage (~508/518 tests) by reverse-engineering the protocol through real SharePoint VM captures. Current State: ~381+ operations across 15+ object models, ~98% test pass rate

Decision 2: 95%+ Coverage = Production Ready

Philosophy: "Acceptable Excellence"

Coverage < 80%  = Unacceptable (incomplete)
Coverage 80-90% = Good (production-ready with workarounds)  ← RestSoap (78.7%)
Coverage 90-95% = Very Good (minor edge cases only)
Coverage 95%+   = Excellent (production quality)            ← Main (97.6%)

Why Not 100%? - Last 5% of functionality takes 50% of effort - Real-world usage doesn't require 100% - Better to have 80% working than 100% partially broken - Document gaps as known limitations - Focus on highest-ROI features instead

Decision 3: Async-First, No Blocking Calls

Pattern: All I/O is async/await

// ✅ Good: Async all the way
public async Task<SPWeb> GetWebAsync(string webId)
{
    return await _storage.LoadWebAsync(webId);
}

// ❌ Avoided: Blocking calls
public SPWeb GetWeb(string webId)
{
    return _storage.LoadWebAsync(webId).Result;  // BLOCKS!
}

Benefits: - Better scalability (doesn't block thread pool) - Responsive (doesn't starve other requests) - ASP.NET Core convention - Enables cancellation tokens

Decision 4: Testing Variance Tolerance

3-Run Median Methodology:

Test Type Variance Reason Assessment
In-Process (Main) ±0 Direct server, no HTTP Perfect
External HTTP (RestSoap) ±3.5 HTTP layer variance Excellent
CSOM/PnP ±5-15 Protocol complexity Acceptable

Why Variance is Normal: - HTTP request timing varies - Async task scheduling differs - Test isolation creates minor state differences - Concurrent file I/O effects

Variance is NOT: - ❌ Random test failures - ❌ Sign of unstable infrastructure - ❌ Reason to revert working code - ❌ Indicating bugs in implementation


Layer 3: Business Logic & Initialization

CSOM Query Processing

Converts CSOM ClientRequest objects to SharePoint operations:

public class CsomProcessor
{
    // Processes: Query, Load, SetProperty, Update, etc.
    // Returns: CsomResponse in special binary JSON format
    // Handles: Complex object graphs, collections, method invocations
}

SOAP Service Pattern

Follows SharePoint SOAP conventions:

// Example: Lists.asmx GetList
public async Task<XmlDocument> GetListAsync(string listName)
{
    var list = await _storage.GetListByTitleAsync(listName);  // Service layer
    return XmlSerializationHelper.SerializeListAsXml(list);
}

MockData Initialization

Bootstraps server with 531MB of test data: - Default sites and webs - Sample lists with items - Test users and groups - Sample files and folders - Event receiver configurations


Layer 4: Endpoints (REST, SOAP, CSOM, PnP)

REST API

  • Framework: ASP.NET Core MVC
  • Format: JSON
  • Patterns: OData ($filter, $select, $expand)
  • Status Codes: 200, 404, 400, 500, etc.

SOAP Services

  • Framework: XML serialization
  • Services: 26 implemented
  • Patterns: CAML queries, method invocation
  • Authentication: NTLM, Basic

CSOM

  • Protocol: XML request, JSON response
  • Complexity: Highest (property ordering critical)
  • Coverage: ~98% (~508/518 tests, 381+ operations)
  • Requirement: [JsonProperty(Order = X)] on all models

PnP PowerShell

  • Abstraction: Highest level (wraps CSOM + REST)
  • Framework: PnP.PowerShell 3.1.0 (net481)
  • Coverage: ~88% (196/273 tests, 27F/50S)
  • Limitation: 27 failures + 50 skipped blocked by client-side PnP 3.x / CSOM protocol issues

Key Patterns

Pattern 1: Service Layer Abstraction

Business logic in services, controllers delegate:

public class ListsController : ControllerBase
{
    public async Task<IActionResult> GetLists()
    {
        return Ok(await _listsService.GetListsAsync());
    }
}

Pattern 2: Consistent Error Handling

throw new SPException(SPErrorCode.ListNotFound, "List not found");

Pattern 3: Defensive Defaults

public List<SPListItem> Items { get; } = new();  // Not null

Pattern 4: Configuration Over Convention

All settings in appsettings.json


Performance Optimizations

  1. ReaderWriterLockSlim - Multiple concurrent reads, single writer
  2. Lazy Loading - Collections load on-demand
  3. Connection Pooling - Kestrel reuses TCP connections
  4. JSON Caching - Frequently-accessed objects cached

Security Architecture

Authentication Schemes

  • NTLM (Windows)
  • Basic (username/password)
  • Bearer (JWT tokens)
  • Forms (ASP.NET)

Authorization

  • Role-based access control (RBAC)
  • Object-level permissions
  • Permission inheritance
  • Unique role assignments

Input Validation

  • Path traversal prevention
  • XML/JSON parsing safety
  • URL validation
  • Sanitization of user input

Summary

The architecture balances: - Simplicity - 4 clear layers - Testability - Abstracted storage, DI - Maintainability - Single source of truth - Performance - Async, pooling, caching - Security - Multiple auth, RBAC - Pragmatism - Accept constraints, focus on ROI

Philosophy: Production-ready software > Theoretical perfection


See Also: - QUICK_START.md - Getting started - KNOWN_LIMITATIONS.md - Known constraints - API_REFERENCE.md - Endpoint documentation - DEPLOYMENT_GUIDE.md - Running in production