AyCode.Core/docs/ARCHITECTURE.md

5.0 KiB

Architecture

Framework vs. Consumer Boundary

This solution is Layer 0 — Core framework. Consumers (plural, unknown) reference it.

Layer hierarchy

Layer 0 — Core framework           this solution (AyCode.Core)
Layer 1 — UI framework             e.g. AyCode.Blazor — Blazor/MAUI bases
Layer 2 — Domain framework         e.g. Mango.Nop.Core — NopCommerce-plugin bases
Layer 3 — Consumer application     the actual business app

Dependencies flow downward only. A consumer CAN reference this framework; this framework CAN NEVER reference a consumer.

What belongs here vs. in a consumer

Yes, framework:

  • Abstract base classes with hooks for consumer override
  • Interfaces and contracts
  • Options classes for consumer configuration
  • Generic logic parameterized by consumer types

No, consumer only:

  • Business logic
  • Consumer-named types or namespaces
  • Hardcoded URLs, tenants, or product IDs

Minimum-boilerplate ideal

Well-designed framework → minimal consumer setup. Aim for:

// Consumer Program.cs — ideal pattern
services.Configure<AcXxxOptions>(config.GetSection("AyCode:Xxx"));
services.AddAcXxxFactory<MyConcreteType>();

Verbose consumer code = framework incomplete. Promote recurring patterns via extension methods, default-providing base classes, or options classes.

Promotion pattern

When a pattern appears in 2+ consumer projects:

  1. Identify generic vs. consumer-specific parts
  2. Move generic part → appropriate framework layer (abstract base, options, or extension)
  3. Leave specific part in consumer (override or configure)

Framework design follows "write the base first, derive the specific later" — when planning a new feature, first consider whether the generic part fits the framework, only then implement consumer-specific derived code.

Dependency Graph

AyCode.Utils (zero dependencies)
    ↑
AyCode.Interfaces → AyCode.Entities → AyCode.Models
    ↑                    ↑                  ↑
AyCode.Core ─────────────┘                  │
    ↑                                       │
AyCode.Database ────────────────────────────┘
    ↑
AyCode.Services ← AyCode.Services.Server

Rule: Dependencies flow upward only. Lower layers never reference higher layers.

Project Roles

Foundation Layer

  • AyCode.Utils — Zero-dependency utilities. String/DateTime extensions, lock wrappers.
  • AyCode.Interfaces — Pure interfaces. IId<T> is the root abstraction.
  • AyCode.Entities — Abstract generic entity classes. Never instantiated directly.
  • AyCode.Models — DTOs and view models for service boundaries.

Core Layer

  • AyCode.Core — Serializers (Binary, JSON, Toon), compression (Brotli, GZip, LZ4), logging framework, constants, validation.
  • AyCode.Core.Serializers.SourceGenerator — Roslyn incremental generator. Targets netstandard2.0. Generates IGeneratedBinaryWriter / IGeneratedBinaryReader for [AcBinarySerializable] types.

Data Layer

  • AyCode.Database — EF Core with generic DAL pattern. Session for reads, Transaction for writes. DAL pooling via PooledDal.

Service Layer

  • AyCode.Services — Client-side: SignalR client, login service, loggers.
  • AyCode.Services.Server — Server-side: SignalR hub with custom binary protocol, email (SendGrid), JWT auth.
  • AyCode.Models.Server/DynamicMethods — Reflection-based tag→method dispatch used by the SignalR hub.

SignalR Dispatch: Both directions use a single method OnReceiveMessage(int messageTag, int? requestId, SignalParams signalParams, object data) with integer tag-based routing instead of standard Hub methods. Write path: zero-copy via AcBinarySerializer.Serialize(value, output) directly to pipe. Read path: protocol eagerly deserializes data to typed object via SignalParams.SignalDataType, or returns raw byte[] for IsRawBytesData/byte[] fast-path. See AyCode.Services/docs/SIGNALR/README.md for full details.

Server Extensions

  • AyCode.Core.Server, AyCode.Interfaces.Server, AyCode.Entities.Server, AyCode.Models.Server — Server-only additions that don't belong in shared code.

Serialization Architecture

Three serializers share a common infrastructure but serve different goals:

Serializer Primary Goal Use Case
AcBinary Speed Wire protocol, SignalR, storage
AcJson Compatibility REST APIs, debugging, interop
Toon LLM Accuracy AI context, schema documentation

Generic Entity Pattern

Entities use composition via generic type parameters:

// Interface layer
interface IAcUser<TProfile, TCompany> : IId<Guid> { ... }

// Entity layer (abstract)
abstract class AcUser<TProfile, TCompany, TUserToCompany, TAddress> { ... }

// Consuming project (concrete)
class User : AcUser<Profile, Company, UserToCompany, Address> { ... }

This allows the framework to define relationships without knowing concrete types.