Skip to content

Instantly share code, notes, and snippets.

@prabh-62
Created May 5, 2026 14:26
Show Gist options
  • Select an option

  • Save prabh-62/cc163a3b61079d6842cf8e507a226ec3 to your computer and use it in GitHub Desktop.

Select an option

Save prabh-62/cc163a3b61079d6842cf8e507a226ec3 to your computer and use it in GitHub Desktop.
Put most code in class libraries (.NET)

.NET architecture: class libraries, entry points, and thin controllers

Put most code in class libraries so new hosts (API, worker, AI, mobile) plug in without duplicating rules. Controllers should stay thin; fat controllers make reuse and testing expensive.


1. Core idea

Business rules and workflows live in class libraries. Entry points are thin shells that connect those libraries to HTTP, queues, schedules, chat, etc.

Layer Examples Role
Domain Entities, value objects, domain rules No dependency on infrastructure frameworks
Application Use cases, validation, orchestration, ports (interfaces) “What the system does”
Infrastructure EF, HTTP clients, messaging, file storage Implements application ports
Entry points ASP.NET Web API, worker service, Azure Functions, AI host DI, routing, HTTP status, wiring — not the heart of the feature

Mental model: treat class libraries as the product; APIs and workers are cables plugged into that product.


2. Why libraries matter (especially for extra entry points later)

  1. Entry points multiply; rules should not. Today one API; tomorrow a background worker, new public API, internal tool, or AI agent. Each is a new host. Logic trapped in Controllers gets copied or forked; fixes ship in multiple places.

  2. One place for correctness. Use-case code (validation, authz, transactions, invariants) is hard enough once. Libraries + unit tests give one behavior everywhere.

  3. Adding a host is integration work, not archaeology. New project: reference libraries → register DI → call the same application services the API already uses.

  4. Teams can evolve surfaces independently while keeping auditing and rules centralized.

Mobile note: clients often call HTTP APIs rather than referencing the same server DLLs (platform, security, trimming). Shared small netstandard models are optional; the server truth still lives in libraries.


3. Diagram

Libraries (large) → Entry points (thin)

┌─────────────────────────────────────────────────────────────┐
│  CLASS LIBRARIES — most code / business rules               │
│  ┌──────────┐   ┌──────────────┐   ┌─────────────────────┐  │
│  │ Domain   │ → │ Application  │ ← │ Infrastructure      │  │
│  └──────────┘   └──────────────┘   └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
          ↑                    ↑
          │                    │
┌─────────┴────────────────────┴──────────────────────────────┐
│  ENTRY POINTS — thin adapters (routing, DI, HTTP status)    │
│   [ Web API ]  [ Worker ]  [ AI agent ]  [ Mobile → API ]    │
└──────────────────────────────────────────────────────────────┘

Hosts should depend on Application (and composition root wires Infrastructure), not cram domain logic into controllers.


4. Mermaid

flowchart TB
  subgraph libs["Class libraries"]
    D[Domain]
    A[Application]
    I[Infrastructure]
    D --> A
    I --> A
  end

  subgraph hosts["Entry points — thin"]
    API[Web API]
    W[Worker]
    AI[AI host]
    Mob[Mobile client]
  end

  API --> A
  W --> A
  AI --> A
  Mob -.->|HTTP| API
Loading

5. Arguments

  • Libraries: reusable core, fast unit tests, consistent rules across hosts.
  • Hosts: boring glue; new “cable” is cheap when the core already exists.
  • Extract later sounds fine until duplicate behavior ships in production during migration.

6. Why logic ends up in controllers (and why that hurts)

Why it drifts

  • Controllers are the first place things work (routing, binding), so the next if lands there.
  • Deadlines favor finishing in the action.
  • Tutorials often show fat actions for brevity.

Why it hurts

  • Reuse: workers and other hosts cannot cleanly call OrdersController.Post.
  • Tests: proving rules requires HTTP + auth + routing instead of testing plain classes.
  • Consistency: two actions can implement “cancel order” slightly differently.

Direction

Controller Application library
Status codes, routes, [FromBody] Validation, rules, transactions
Map DTO ↔ command Orchestration
Ok / Problem Domain + infrastructure via abstractions

Practical rule: if it would still be correct without HTTP (same inputs/outputs), it does not belong in the controller long-term.


@prabh-62
Copy link
Copy Markdown
Author

prabh-62 commented May 5, 2026

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment