Modular monolith architecture. Module isolation, inter-module communication, shared kernel, and migration path to microservices. Trigger: modular monolith, modules, module isolation, module communication.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
src/
{Company}.{Domain}.WebApi/ # Host — composition root
{Company}.{Domain}.SharedKernel/ # Base classes, common interfaces
Modules/
Orders/
{Company}.{Domain}.Orders.Api/ # Public contracts (DTOs, interfaces)
{Company}.{Domain}.Orders.Core/ # Domain + Application logic
{Company}.{Domain}.Orders.Infrastructure/ # Data access, external services
Inventory/
{Company}.{Domain}.Inventory.Api/
{Company}.{Domain}.Inventory.Core/
{Company}.{Domain}.Inventory.Infrastructure/
Shipping/
{Company}.{Domain}.Shipping.Api/
{Company}.{Domain}.Shipping.Core/
{Company}.{Domain}.Shipping.Infrastructure/
// Modules/Orders/Orders.Api/IOrderModule.cs
namespace {Company}.{Domain}.Orders.Api;
// Only this project is referenced by other modules
public interface IOrderModule
{
Task<OrderSummaryDto?> GetOrderSummaryAsync(
Guid orderId, CancellationToken ct);
Task<bool> OrderExistsAsync(Guid orderId, CancellationToken ct);
}
public sealed record OrderSummaryDto(
Guid Id, string CustomerName, decimal Total, string Status);
// Modules/Orders/Orders.Core/OrderModule.cs
namespace {Company}.{Domain}.Orders.Core;
internal sealed class OrderModule(OrderDbContext db) : IOrderModule
{
public async Task<OrderSummaryDto?> GetOrderSummaryAsync(
Guid orderId, CancellationToken ct)
{
return await db.Orders
.Where(o => o.Id == orderId)
.Select(o => new OrderSummaryDto(
o.Id, o.CustomerName, o.Total, o.Status.ToString()))
.FirstOrDefaultAsync(ct);
}
public async Task<bool> OrderExistsAsync(
Guid orderId, CancellationToken ct)
{
return await db.Orders.AnyAsync(o => o.Id == orderId, ct);
}
}
// Modules/Orders/Orders.Infrastructure/OrderDbContext.cs
namespace {Company}.{Domain}.Orders.Infrastructure;
internal sealed class OrderDbContext(
DbContextOptions<OrderDbContext> options) : DbContext(options)
{
public DbSet<Order> Orders => Set<Order>();
public DbSet<OrderItem> OrderItems => Set<OrderItem>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("orders"); // schema isolation
modelBuilder.ApplyConfigurationsFromAssembly(
typeof(OrderDbContext).Assembly);
}
}
// SharedKernel/Events/IIntegrationEvent.cs
public interface IIntegrationEvent : INotification
{
Guid EventId { get; }
DateTimeOffset OccurredAt { get; }
}
// Orders module publishes
public sealed record OrderPlacedIntegrationEvent(
Guid OrderId,
string CustomerName,
decimal Total) : IIntegrationEvent
{
public Guid EventId { get; } = Guid.NewGuid();
public DateTimeOffset OccurredAt { get; } = DateTimeOffset.UtcNow;
}
// Inventory module handles
internal sealed class ReserveInventoryOnOrderPlaced(
InventoryDbContext db)
: INotificationHandler<OrderPlacedIntegrationEvent>
{
public async Task Handle(
OrderPlacedIntegrationEvent notification,
CancellationToken ct)
{
// Reserve inventory for the order
var reservation = InventoryReservation.Create(
notification.OrderId, notification.OccurredAt);
db.Reservations.Add(reservation);
await db.SaveChangesAsync(ct);
}
}
// Each module has a registration extension
// Modules/Orders/Orders.Infrastructure/OrderModuleRegistration.cs
public static class OrderModuleRegistration
{
public static IServiceCollection AddOrderModule(
this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("Default"),
sql => sql.MigrationsHistoryTable(
"__EFMigrationsHistory", "orders")));
services.AddScoped<IOrderModule, OrderModule>();
services.AddMediatR(cfg =>
cfg.RegisterServicesFromAssembly(
typeof(OrderModule).Assembly));
return services;
}
}
// WebApi/Program.cs
builder.Services
.AddOrderModule(builder.Configuration)
.AddInventoryModule(builder.Configuration)
.AddShippingModule(builder.Configuration);
// SharedKernel/BaseEntity.cs
public abstract class BaseEntity
{
public Guid Id { get; protected set; } = Guid.NewGuid();
private readonly List<IDomainEvent> _domainEvents = [];
public IReadOnlyList<IDomainEvent> DomainEvents =>
_domainEvents.AsReadOnly();
protected void AddDomainEvent(IDomainEvent e) => _domainEvents.Add(e);
public void ClearDomainEvents() => _domainEvents.Clear();
}
// SharedKernel/IUnitOfWork.cs
public interface IUnitOfWork
{
Task<int> SaveChangesAsync(CancellationToken ct = default);
}
InternalsVisibleTo for tests only)Modules/ folder structure in the solution| Question | Answer |
|---|---|
| How do modules communicate? | Integration events (async) or module interfaces (sync reads) |
| Can modules share a database? | Same server is OK, but use separate schemas |
| What goes in SharedKernel? | Base classes, common interfaces, integration event contracts |
| When to split into microservices? | When independent deployment or scaling is needed |
| How to handle transactions across modules? | Eventual consistency via events; avoid distributed transactions |