원클릭으로
clean-architecture
Use when implementing or enforcing Clean Architecture with 4-layer separation and dependency inversion.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when implementing or enforcing Clean Architecture with 4-layer separation and dependency inversion.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when adding caching to .NET APIs or optimizing response times with distributed cache, output cache, or ETags.
Use when configuring API response formats, custom formatters, or Accept header handling.
Use when building controller-based REST APIs with action results, model binding, or MediatR integration.
Use when creating RESTful API controllers with MediatR dispatch and ProblemDetails error responses.
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when building minimal API endpoints with route groups, filters, or TypedResults.
| name | clean-architecture |
| description | Use when implementing or enforcing Clean Architecture with 4-layer separation and dependency inversion. |
| metadata | {"category":"architecture","agent":"dotnet-architect","when-to-use":"When designing or reviewing Clean Architecture layer structure and dependency rules"} |
src/
{Company}.{Domain}.Domain/ # Entities, value objects, domain events, interfaces
{Company}.{Domain}.Application/ # Commands, queries, handlers, DTOs, validators
{Company}.{Domain}.Infrastructure/ # EF Core, external services, file system
{Company}.{Domain}.WebApi/ # Endpoints, middleware, DI composition root
WebApi → Infrastructure → Application → Domain
↑
WebApi → Application → Domain
Domain references nothingApplication references DomainInfrastructure references Application + DomainWebApi references all layers (composition root)// Domain/Entities/Order.cs
namespace {Company}.{Domain}.Domain.Entities;
public sealed class Order : BaseEntity, IAggregateRoot
{
public string CustomerName { get; private set; } = default!;
public OrderStatus Status { get; private set; } = OrderStatus.Draft;
public decimal Total { get; private set; }
private readonly List<OrderItem> _items = [];
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
private Order() { } // EF Core constructor
public static Order Create(string customerName)
{
var order = new Order { CustomerName = customerName };
order.AddDomainEvent(new OrderCreatedEvent(order.Id));
return order;
}
public void AddItem(Guid productId, int quantity, decimal price)
{
Guard.Against.NegativeOrZero(quantity);
_items.Add(new OrderItem(productId, quantity, price));
Total = _items.Sum(i => i.Quantity * i.Price);
}
}
// Domain/Interfaces/IOrderRepository.cs
public interface IOrderRepository
{
Task<Order?> FindAsync(Guid id, CancellationToken ct = default);
Task<List<Order>> ListAsync(CancellationToken ct = default);
void Add(Order order);
}
// Domain/Interfaces/IUnitOfWork.cs
public interface IUnitOfWork
{
Task<int> SaveChangesAsync(CancellationToken ct = default);
}
// Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs
namespace {Company}.{Domain}.Application.Orders.Commands.CreateOrder;
public sealed record CreateOrderCommand(string CustomerName) : IRequest<Guid>;
// Application/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs
internal sealed class CreateOrderCommandHandler(
IOrderRepository repository,
IUnitOfWork unitOfWork) : IRequestHandler<CreateOrderCommand, Guid>
{
public async Task<Guid> Handle(
CreateOrderCommand request, CancellationToken ct)
{
var order = Order.Create(request.CustomerName);
repository.Add(order);
await unitOfWork.SaveChangesAsync(ct);
return order.Id;
}
}
// Application/Orders/Commands/CreateOrder/CreateOrderCommandValidator.cs
public sealed class CreateOrderCommandValidator
: AbstractValidator<CreateOrderCommand>
{
public CreateOrderCommandValidator()
{
RuleFor(x => x.CustomerName).NotEmpty().MaximumLength(200);
}
}
// Infrastructure/Persistence/AppDbContext.cs
namespace {Company}.{Domain}.Infrastructure.Persistence;
internal sealed class AppDbContext(
DbContextOptions<AppDbContext> options) : DbContext(options), IUnitOfWork
{
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(AppDbContext).Assembly);
}
}
// Infrastructure/Persistence/Repositories/OrderRepository.cs
internal sealed class OrderRepository(AppDbContext db) : IOrderRepository
{
public async Task<Order?> FindAsync(Guid id, CancellationToken ct)
=> await db.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
public async Task<List<Order>> ListAsync(CancellationToken ct)
=> await db.Orders.ToListAsync(ct);
public void Add(Order order) => db.Orders.Add(order);
}
// Infrastructure/DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("Default")));
services.AddScoped<IUnitOfWork>(sp =>
sp.GetRequiredService<AppDbContext>());
services.AddScoped<IOrderRepository, OrderRepository>();
return services;
}
}
// WebApi/Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddApplicationServices()
.AddInfrastructure(builder.Configuration);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.MapEndpointGroups();
app.Run();
new SqlConnection)Domain, Application, Infrastructure, WebApi/Api/PresentationIUnitOfWork interface in Domain or Application layerusing statements for EF Core or infrastructure{Company}.{Domain}.{Layer}| Question | Answer |
|---|---|
| Where do entities go? | Domain |
| Where do repository interfaces go? | Domain (per aggregate root) |
| Where do DTOs go? | Application |
| Where do MediatR handlers go? | Application |
| Where does DbContext go? | Infrastructure |
| Where does DI registration go? | Each layer has its own + WebApi composes |
| Where does validation go? | Application (FluentValidation) |