Architecture
managedcode/dotnet-skills
Design or review .NET solution architecture across modular monoliths, clean architecture, vertical slices, microservices, DDD, CQRS, and cloud-native boundaries without over-engineering.
A skill your agent uses when domain logic leaks into API/Infrastructure, project references violate layer boundaries, or you need to decide between CQS (always), CQRS bus (complex domains), and DDD…
$ npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install SebastienDegodez/copilot-instructions clean-architecture-dotnet --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/SebastienDegodez/copilot-instructions.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet .claude/skills/clean-architecture-dotnet && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "clean-architecture-dotnet" agent skill from https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet into .claude/skills/clean-architecture-dotnet/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "clean-architecture-dotnet", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnetType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install SebastienDegodez/copilot-instructions clean-architecture-dotnet --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SebastienDegodez/copilot-instructions.git skills-src && mkdir -p .agents/skills && cp -r skills-src/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet .agents/skills/clean-architecture-dotnet && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "clean-architecture-dotnet" agent skill from https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet into .agents/skills/clean-architecture-dotnet/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "clean-architecture-dotnet", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install SebastienDegodez/copilot-instructions clean-architecture-dotnet --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SebastienDegodez/copilot-instructions.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet .cursor/skills/clean-architecture-dotnet && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "clean-architecture-dotnet" agent skill from https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet into .cursor/skills/clean-architecture-dotnet/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "clean-architecture-dotnet", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/SebastienDegodez/copilot-instructions.git --path plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install SebastienDegodez/copilot-instructions clean-architecture-dotnet --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SebastienDegodez/copilot-instructions.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet .gemini/skills/clean-architecture-dotnet && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "clean-architecture-dotnet" agent skill from https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet into .gemini/skills/clean-architecture-dotnet/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "clean-architecture-dotnet", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install SebastienDegodez/copilot-instructions clean-architecture-dotnetInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/SebastienDegodez/copilot-instructions.git skills-src && mkdir -p .github/skills && cp -r skills-src/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet .github/skills/clean-architecture-dotnet && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "clean-architecture-dotnet" agent skill from https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet into .github/skills/clean-architecture-dotnet/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "clean-architecture-dotnet", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install SebastienDegodez/copilot-instructions clean-architecture-dotnet --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SebastienDegodez/copilot-instructions.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet .opencode/skills/clean-architecture-dotnet && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "clean-architecture-dotnet" agent skill from https://github.com/SebastienDegodez/copilot-instructions/tree/main/plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet into .opencode/skills/clean-architecture-dotnet/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "clean-architecture-dotnet", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
clean-architecture-dotnetA skill your agent uses when domain logic leaks into API/Infrastructure, project references violate layer boundaries, or you need to decide between CQS (always), CQRS bus (complex domains), and DDD…
Clean Architecture Dotnet is an agent skill from SebastienDegodez/copilot-instructions. Use when domain logic leaks into API/Infrastructure, project references violate layer boundaries, or you need to decide between CQS (always), CQRS bus (complex domains), and DDD patterns (invariants and events).
Its SKILL.md is about 5.1k tokens, which your agent loads only when the skill is triggered. The skill folder holds 37 other files, including scripts and reference files (for example `references/architecture-layers.md`, `references/convention-based-di.md` and `references/cqrs-patterns.md`).
It sits in Development, covering Design patterns, Domain-driven design and Event-driven systems. It works with .NET. The repository describes itself as: A comprehensive codebase of best practices, coding rules, and workflow automation for AI-assisted development with GitHub Copilot. Includes DDD, Clean Architecture, testing… The licence is Apache-2.0.
Read from SKILL.md and the folder at commit 0f0dccf. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 2 files in scripts/ (C#, PowerShell and Shell, from the files we listed), which the agent can run.
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Clean Architecture Dotnet loads about 5.1k tokens when it runs, and up to ~22k if it reads all its reference files. Until then it costs about 59 tokens; SKILL.md has 1,651 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from SebastienDegodez/copilot-instructions at commit 0f0dccf, republished under its Apache-2.0 licence (© SebastienDegodez). 1,651 words, ~5,134 tokens.
.claude/skills/clean-architecture-dotnet/SKILL.md (or your agent's skills folder). This skill also uses 31 other files; get the full folder from GitHub.Clean Architecture organizes code into independent layers (Domain → Application → Infrastructure → API).
The Iron Law: Violating layer boundaries is a failure. If API references Application or Domain references Infrastructure, delete the change and start over.
Concrete violations that break the Iron Law:
using MyApp.Infrastructure;in a Domain class — Infrastructure must never flow inward.using MyApp.Application;in an API endpoint file — API must only interact viaICommandBus/IQueryBusor an injected use case interface.new AppDbContext()inside a Controller — bypasses both Infrastructure and Application layers.HttpClientinjected directly into a Domain or Application class — HTTP belongs in Infrastructure behind an interface.Fix: move the dependency to the correct outer layer and expose it through an interface pointing inward.
When NOT to add CQRS or DDD:
IQueryBus requiredThese three patterns are independent tools. Choose based on what the codebase actually needs.
| Pattern | Rule | Add it when… | Skip it when… |
|---|---|---|---|
| CQS (always) | Every handler/method is either a command (void) or a query (returns value) — never both | Always — zero exceptions | Never skip |
| CQRS + Bus (optional) | Separate ICommandBus / IQueryBus routing commands and queries through a pipeline | Cross-cutting concerns (validation, logging pipelines), large teams writing many use cases, read/write model asymmetry | Simple CRUD; < 3 use cases; no cross-cutting pipeline needed |
| DDD (optional) | Aggregates, factory methods, value objects, domain events | Domain has invariants (rules that must be enforced on every state change), state machines, rich business logic | Entities are pure data (no if/throw to protect state); CRUD-only feature |
Quick rule of thumb:
"Does this feature have a business rule that could be violated?"
— No → CQS Application Service (Pattern A).
— Yes → Domain Aggregate + CQS or CQRS depending on scale (Pattern B).
| Use | When |
|---|---|
sealed class Foo : AggregateRoot + factory method | The object has domain invariants, raises events, or owns child entities |
Plain sealed record Foo(...) (POCO) | Pure data carrier, no invariants, used only inside Application or API (DTOs, ViewModels) |
sealed class Bar : ValueObject | Immutable, identity-by-value, represents a domain concept (e.g., PolicyNumber, Horsepower) |
Rule of thumb: if you're tempted to add an if or throw to protect the object's state — it's a Domain object (aggregate or value object), not a POCO.
Aggregate vs POCO test: write the business method first. If you never need
if (condition) throw new DomainException(...)to protect state, the object has no invariants — make it a plainsealed record, not anAggregateRoot. DDD complexity is the cure for invariants, not for data.
digraph clean_arch {
"Domain has invariants?" [shape=diamond];
"Pattern A: CQS App Service" [shape=box];
"Pattern B: DDD + CQRS?" [shape=diamond];
"Define Aggregate in Domain" [shape=box];
"Create Handler in Application" [shape=box];
"Add CQRS Bus? (optional)" [shape=diamond];
"Route via ICommandBus/IQueryBus" [shape=box];
"Inject handler directly" [shape=box];
"Expose in API" [shape=box];
"Domain has invariants?" -> "Pattern A: CQS App Service" [label="No (CRUD)"];
"Domain has invariants?" -> "Pattern B: DDD + CQRS?" [label="Yes"];
"Pattern A: CQS App Service" -> "Expose in API";
"Pattern B: DDD + CQRS?" -> "Define Aggregate in Domain";
"Define Aggregate in Domain" -> "Create Handler in Application";
"Create Handler in Application" -> "Add CQRS Bus? (optional)";
"Add CQRS Bus? (optional)" -> "Route via ICommandBus/IQueryBus" [label="Yes (cross-cutting)"];
"Add CQRS Bus? (optional)" -> "Inject handler directly" [label="No"];
"Route via ICommandBus/IQueryBus" -> "Expose in API";
"Inject handler directly" -> "Expose in API";
}Use when the feature has no business invariants. Layer boundaries are maintained — no CQRS bus needed.
CQS rule: each method is either a command (void / Task) or a query (returns a value) — never both.
Naming: call it *UseCase, not *Service. "Service" implies stateful or cross-cutting infrastructure; a use case class orchestrates one bounded interaction.
// Application/Contacts/ContactUseCase.cs — plain use case, no bus
public sealed class ContactUseCase
{
private readonly IContactRepository _repository; // interface defined in Application (see Interface Placement)
public ContactUseCase(IContactRepository repository)
{
_repository = repository;
}
// Command — void (CQS)
public async Task CreateAsync(Guid id, string name, CancellationToken ct)
{
await _repository.AddAsync(new Contact(new ContactId(id), name), ct);
}
// Query — maps domain object to ViewModel in the use case (Option 1)
public async Task<ContactViewModel?> GetByIdAsync(Guid id, CancellationToken ct)
{
var contact = await _repository.FindAsync(new ContactId(id), ct);
return contact is null ? null : new ContactViewModel(contact.Id.Value, contact.Name);
}
}
// API/ContactsEndpoints.cs — inject the use case directly (no bus)
app.MapPost("/contacts", async (CreateContactRequest req, ContactUseCase uc) =>
{
var id = Guid.NewGuid();
await uc.CreateAsync(id, req.Name, default);
return Results.Created($"/contacts/{id}", null);
});
app.MapGet("/contacts/{id:guid}", async (Guid id, ContactUseCase uc) =>
await uc.GetByIdAsync(id, default) is { } vm ? Results.Ok(vm) : Results.NotFound());Option 2 — read-optimized query: for read-heavy paths, skip the domain object entirely. Define a dedicated read interface in Application; Infrastructure implements it directly (e.g., a raw SQL/EF projection).
// Application/Contacts/IContactReadService.cs — interface in Application, no domain object involved
public interface IContactReadService
{
Task<ContactViewModel?> GetByIdAsync(ContactId id, CancellationToken ct = default);
}
// Infrastructure/Contacts/ContactReadService.cs — implementation maps directly from DB row to ViewModel
public sealed class ContactReadService : IContactReadService
{
private readonly AppDbContext _db;
public ContactReadService(AppDbContext db)
{
_db = db;
}
public async Task<ContactViewModel?> GetByIdAsync(ContactId id, CancellationToken ct)
{
return await _db.Contacts
.Where(c => c.Id == id.Value)
.Select(c => new ContactViewModel(c.Id, c.Name))
.FirstOrDefaultAsync(ct);
}
}Rule of thumb:
IContactRepository(write path) returns domain objects.IContactReadService(read path) returns ViewModels. Both interfaces live in Application; implementations live in Infrastructure.
Use when the domain has invariants, state transitions, or events. CQRS bus is optional even here — add it when you need a cross-cutting pipeline.
Add
ICommandBus/IQueryBusonly when ALL of the following are true:
- You need a cross-cutting pipeline for ALL commands/queries (e.g., logging every command, input validation via pipeline behaviours, performance metrics).
- You have multiple use cases (>3) that all benefit from this pipeline — the overhead is justified by scale.
- Your read and write models are structurally different (CQRS read-model separation), or your team is large enough that different people own the command and query paths.
If only one condition is true, use a decorator or middleware instead — don't build a bus for a single use case.
CQS rule for commands: a command must be void — it does not return a value. The caller is responsible for generating the ID and including it in the command.
// Application/Features/Orders/PlaceOrderCommand.cs — ID is part of the command
public sealed record PlaceOrderCommand(OrderId OrderId, string CustomerName);
// Application/Features/Orders/PlaceOrderCommandHandler.cs
public sealed class PlaceOrderCommandHandler : ICommandHandler<PlaceOrderCommand>
{
private readonly IOrderRepository _repository; // IOrderRepository defined in Domain (write path) — see Interface Placement
public async Task HandleAsync(PlaceOrderCommand cmd, CancellationToken ct)
{
var order = Order.Create(cmd.OrderId, cmd.CustomerName);
await _repository.AddAsync(order, ct);
}
}
// API/OrdersEndpoints.cs — caller generates the ID, sends it via ICommandBus (optional)
app.MapPost("/orders", async (PlaceOrderCommand cmd, ICommandBus bus) =>
{
await bus.PublishAsync(cmd);
return Results.Created($"/orders/{cmd.OrderId}");
});
// Queries follow the same rule — inject IQueryBus if using CQRS bus
app.MapGet("/orders/{id}", async (Guid id, IQueryBus bus)
=> Results.Ok(await bus.SendAsync<GetOrderQuery, OrderViewModel>(new GetOrderQuery(new OrderId(id)))));
// Both buses resolve via Infrastructure DI. API never references Application assembly.| Layer | Purpose | Allowed Dependencies |
|---|---|---|
| SharedKernel (multi-context only) | Generic interfaces + base classes only — zero domain logic | None |
| Domain | Pure business logic, aggregates | SharedKernel (optional) |
| Application | Use cases, Handler orchestration | Domain, SharedKernel (optional) |
| Infrastructure | Database, DI registration, CQRS Bus | Application, Domain |
| API | Endpoints, JSON mapping | Infrastructure (Transitive: Application, Domain) |
Use a SharedKernel project when two or more Bounded Contexts need to share handler interfaces or base classes. It must contain zero domain logic.
SharedKernel/
├── Abstractions/
│ ├── ICommandHandler.cs ← generic interface only
│ ├── IQueryHandler.cs
│ ├── ValueObject.cs ← base class, no business logic
│ └── AggregateRoot.cs ← base class, exposes DomainEvents collection
└── Events/
└── DomainEvent.cs ← abstract base for all domain eventsDependency rule for SharedKernel: it depends on nothing. Domain and Application reference it, not the other way around.
// SharedKernel/Abstractions/ICommandHandler.cs — commands are void (CQS)
public interface ICommandHandler<in TCommand>
{
Task HandleAsync(TCommand command, CancellationToken ct = default);
}
// SharedKernel/Abstractions/IQueryHandler.cs — queries return a result
public interface IQueryHandler<in TQuery, TResult>
{
Task<TResult> HandleAsync(TQuery query, CancellationToken ct = default);
}
// Each context's Application layer implements it:
// Orders.Application/Features/PlaceOrder/PlaceOrderCommandHandler.cs
using SharedKernel.Abstractions;
public sealed class PlaceOrderCommandHandler
: ICommandHandler<PlaceOrderCommand> { }What NEVER goes in SharedKernel: concrete value objects like Money, Address (each context defines its own); aggregate logic; context-specific event types.
Rule: Domain events are declared in the Domain layer and dispatched in the Application layer (from the handler, after the aggregate operation succeeds).
// Domain/Orders/Events/OrderPlacedEvent.cs — sealed, in Domain
public sealed class OrderPlacedEvent : DomainEvent // DomainEvent base from SharedKernel (or Domain if single-context)
{
public OrderId OrderId { get; }
public OrderPlacedEvent(OrderId orderId)
{
OrderId = orderId;
}
}
// Domain/Orders/Order.cs — aggregate raises the event
public sealed class Order : AggregateRoot
{
public OrderId Id { get; }
public CustomerId CustomerId { get; }
// Private constructor — only the factory method can create an instance
private Order(OrderId id, CustomerId customerId)
{
Id = id;
CustomerId = customerId;
}
public static Order Create(OrderId id, CustomerId customerId)
{
var order = new Order(id, customerId);
order.AddDomainEvent(new OrderPlacedEvent(id)); // ← raised in Domain
return order;
}
}
// Application/Features/PlaceOrder/PlaceOrderCommandHandler.cs — handler dispatches
public sealed class PlaceOrderCommandHandler : ICommandHandler<PlaceOrderCommand>
{
private readonly IOrderRepository _repository;
private readonly IDomainEventDispatcher _dispatcher; // interface in Application/Domain
public async Task HandleAsync(PlaceOrderCommand cmd, CancellationToken ct)
{
var order = Order.Create(cmd.OrderId, cmd.CustomerId); // ← ID comes from the command
await _repository.AddAsync(order, ct);
await _dispatcher.DispatchAsync(order.DomainEvents, ct); // ← dispatched in Application
}
}Anti-pattern: Never dispatch events from inside a Domain aggregate method — Domain has no dependency on dispatch infrastructure.
The architecture follows a strict outward-in dependency flow: API → Infrastructure → Application → Domain
CRITICAL: Just because a layer can see another via transitive reference doesn't mean it should use its concrete types.
See Interface Placement for full decision tables and examples.
| Contract | Define in | Key question |
|---|---|---|
| Write repo for a Domain aggregate | Domain | Does it persist an aggregate? |
| Write repo for CRUD entity / read service | Application | No aggregate — Application owns the contract |
ICurrentUser / IUserContext | Application | Use cases need the caller's identity |
| Authentication (token/role gate) | Infrastructure | Technical filter, no business logic |
Authorization quick rule: read access gate → Application use case policy. Mutation invariant → Domain aggregate method. Token/role → Infrastructure middleware.
| Excuse | Reality |
|---|---|
| "Injecting ICommandHandler<,> directly is simpler" | If you're using a CQRS bus, always route through ICommandBus / IQueryBus — consistent indirection, easier to intercept. But if you're not using a bus at all (Pattern A), injecting the Application service directly is correct. |
| "It's just one small service" | Small leaks become circular dependency nightmares. |
| "Referencing Application in API is faster" | It bypasses the Bus/Handler pattern and couples contract to implementation. |
| "Domain needs this NuGet package" | If it's not a primitive/System lib, it doesn't belong in Domain. |
| "Every handler needs a CQRS bus" | Only if you need a cross-cutting pipeline or read/write model separation. Simple CRUD with a CQS Application service is valid and cleaner. |
| "Every entity should be an Aggregate" | Only if the entity has invariants to protect. Plain data without business rules → use records and simple repositories. |
using MyApp.Application; inside API layer filesusing MyApp.Infrastructure; inside Domain layer filesICommandHandler<> or IQueryHandler<,> directly in API endpoints when using a CQRS bus — route through ICommandBus / IQueryBusTask); the ID must be part of the incoming commandICommandBus / IQueryBus to a simple CRUD API with no domain logic, invariants, or cross-cutting pipeline — use a CQS Application use case (Pattern A) instead*Service (e.g., OrderService) — rename to *UseCase; "Service" implies infrastructure or shared state, not a single bounded interaction| Mistake | Fix |
|---|---|
| Handler not found by DI | Handler must be listed explicitly in AddInfrastructure() via AddHandler<T>() |
using MyApp.Application; in API | Remove it — inject ICommandBus / IQueryBus (CQRS) or Application service (CQS) via DI |
| Command handler returns an ID | Commands are void — the caller generates the ID and passes it in the command |
Read result named ProductDto | Name it ProductViewModel to distinguish from transfer objects |
typeof(Product).Assembly in tests | Use typeof(IApplicationMarker).Assembly for reliable discovery |
| Non-sealed Domain classes | All Domain classes must be sealed (enforced by NetArchTest) |
| SharedKernel references Domain | SharedKernel must depend on nothing — if it references Domain, invert: Domain references SharedKernel |
Handler contains if/domain invariant logic | Delegate to Domain aggregate methods — handlers orchestrate only. Exception: Application use cases may contain access policy checks (if (resource.OwnerId != _currentUser.Id) throw) — that is use-case policy, not domain invariant logic |
| Creating Domain aggregates for CRUD entities with no invariants | Use a plain Application service with direct repository access — no aggregate, no events |
| Adding CQRS bus for < 3 use cases with no cross-cutting concerns | Use Pattern A (CQS Application service) — less indirection, same layer safety |
./init-project.sh MyApp)© SebastienDegodez, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 31 other files (scripts, references) in plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet of SebastienDegodez/copilot-instructions.
Open the folder on GitHubat commit 0f0dccf
Clean Architecture Dotnet next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Clean Architecture Dotnet this skillSebastienDegodez/copilot-instructions | 198 | — | ~5.1k | Automated safety check: Pass | Apache-2.0 | |
| Architecturemanagedcode/dotnet-skills | 486 | — | ~659 | Automated safety check: Pass | MIT | |
| Scaffoldcodewithmukesh/dotnet-claude-kit | 756 | — | ~1.7k | Automated safety check: Pass | MIT | |
| Architecture Advisorcodewithmukesh/dotnet-claude-kit | 756 | 1 repos | ~2.9k | Automated safety check: Pass | MIT | |
| Architect ReviewAratKruglik/claude-laravel | 155 | 8 repos | ~2.2k | Automated safety check: Pass | None | |
| Dddcodewithmukesh/dotnet-claude-kit | 756 | 1 repos | ~3k | Automated safety check: Pass | MIT |
managedcode/dotnet-skills
Design or review .NET solution architecture across modular monoliths, clean architecture, vertical slices, microservices, DDD, CQRS, and cloud-native boundaries without over-engineering.
codewithmukesh/dotnet-claude-kit
Architecture-aware feature scaffolding for .NET 10 projects.
codewithmukesh/dotnet-claude-kit
Architecture selection advisor for .NET applications. An agent skill from codewithmukesh/dotnet-claude-kit.
AratKruglik/claude-laravel
Master software architect specializing in modern architecture patterns, clean architecture, microservices, event-driven systems, and DDD.
codewithmukesh/dotnet-claude-kit
Domain-Driven Design tactical patterns for .NET applications.
fmflurry/settings-opencode
Pre-merge code review for .NET 10 pull requests. An agent skill from fmflurry/settings-opencode.
SebastienDegodez/copilot-instructions
A skill your agent uses when configuring Git hooks in .NET projects before team commits occur, to enforce commit message standards and code formatting automatically
SebastienDegodez/copilot-instructions
A skill your agent uses when writing tests from the outside-in, defining behavior before code, or any feature where tests should start from observable business behavior and let internal design emerge
SebastienDegodez/copilot-instructions
A skill your agent uses when building Model Context Protocol (MCP) servers in .NET, configuring tools, transports (SSE/stdio), JSON serialization for AOT, or testing MCP endpoints
SebastienDegodez/copilot-instructions
A skill your agent uses when creating OpenAPI mock examples for Microcks, setting up request/response routing with dispatchers, or mapping request fields to mock responses
SebastienDegodez/copilot-instructions
A skill your agent uses when understanding project composition by language, measuring code change impact, or generating code statistics for CI/CD metrics
SebastienDegodez/copilot-instructions
A skill your agent uses when listing all methods, functions, or classes in a file, exploring unfamiliar code, getting API overviews, or deciding what to read selectively without loading entire files
Works with
Categories
A skill your agent uses when domain logic leaks into API/Infrastructure, project references violate layer boundaries, or you need to decide between CQS (always), CQRS bus (complex domains), and DDD…. Clean Architecture Dotnet is an agent skill from SebastienDegodez/copilot-instructions. Use when domain logic leaks into API/Infrastructure, project references violate layer boundaries, or you need to decide between CQS (always), CQRS bus (complex domains), and DDD patterns (invariants and events).
Clean Architecture Dotnet fits situations like: domain logic leaks into API/Infrastructure; project references violate layer boundaries; you need to decide between CQS (always); CQRS bus (complex domains).
Run `npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a claude-code`. Or copy the skill folder (plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet in SebastienDegodez/copilot-instructions) into .claude/skills/clean-architecture-dotnet in your project. Claude Code loads it when a task matches its description.
Run `npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a codex`. Or copy the skill folder (plugins/csharp-clean-architecture-development/skills/clean-architecture-dotnet in SebastienDegodez/copilot-instructions) into .agents/skills/clean-architecture-dotnet in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add SebastienDegodez/copilot-instructions --skill clean-architecture-dotnet -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/clean-architecture-dotnet, .gemini/skills/clean-architecture-dotnet, .github/skills/clean-architecture-dotnet and .opencode/skills/clean-architecture-dotnet in your project.
Going by SKILL.md and its folder, Clean Architecture Dotnet needs C#, PowerShell and a shell for the scripts in its folder. Our summary lists: A Bash shell; PowerShell.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Clean Architecture Dotnet is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.1k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 17k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Clean Architecture Dotnet: Architecture (managedcode/dotnet-skills, 486 stars), Scaffold (codewithmukesh/dotnet-claude-kit, 756 stars), Architecture Advisor (codewithmukesh/dotnet-claude-kit, 756 stars) and Architect Review (AratKruglik/claude-laravel, 155 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
SebastienDegodez (a GitHub user) maintains it in SebastienDegodez/copilot-instructions, which has 198 GitHub stars. The repository holds 19 skills in this directory. The repository was last updated on October 6, 2026.
Source: SebastienDegodez/copilot-instructions on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.