KO
koan-data-modeling
Aggregate boundaries, relationships, lifecycle hooks, value objects
Install
mkdir -p .claude/skills/koan-data-modeling && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14747" && unzip -o skill.zip -d .claude/skills/koan-data-modeling && rm skill.zipInstalls to .claude/skills/koan-data-modeling
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Aggregate boundaries, relationships, lifecycle hooks, value objects67 charsno explicit “when” trigger
About this skill
Koan Data Modeling
Core Principle
Entities are aggregates that encapsulate business logic and define clear boundaries. Use lifecycle hooks for invariants, value objects for cohesive data, and navigation helpers for relationships.
Quick Reference
Define Aggregate Boundary
public class Order : Entity<Order>
{
public string CustomerId { get; set; } = "";
public Money Total { get; private set; } = new(0m, "USD");
public OrderStatus Status { get; private set; } = OrderStatus.Draft;
// Business methods
public void MarkShipped() => Status = OrderStatus.Shipped;
// Navigation helper
public Task<Customer?> GetCustomer(CancellationToken ct = default) =>
Customer.Get(CustomerId, ct);
// Domain query
public static Task<List<Order>> RecentOrders(int days = 7, CancellationToken ct = default) =>
Query(o => o.Created > DateTimeOffset.UtcNow.AddDays(-days), ct);
}
Value Objects
public record Money(decimal Amount, string Currency);
public record Address(string Street, string City, string State, string Zip);
public class Invoice : Entity<Invoice>
{
public Money Total { get; set; } = new(0m, "USD");
public Address ShippingAddress { get; set; } = new("", "", "", "");
}
Lifecycle Hooks
public static class ProductLifecycle
{
public static void Configure(EntityLifecycleBuilder<Product> pipeline)
{
pipeline.ProtectAll()
.Allow(p => p.Price, p => p.Description)
.BeforeUpsert(async (ctx, next) =>
{
if (ctx.Entity.Price < 0)
throw new InvalidOperationException("Price cannot be negative");
await next();
})
.AfterLoad(ctx => ctx.Entity.FormattedPrice = $"${ctx.Entity.Price:F2}");
}
}
Relationships
public class Todo : Entity<Todo>
{
public string UserId { get; set; } = "";
public string? CategoryId { get; set; }
// Navigation helpers
public Task<User?> GetUser(CancellationToken ct = default) =>
User.Get(UserId, ct);
public Task<Category?> GetCategory(CancellationToken ct = default) =>
string.IsNullOrEmpty(CategoryId) ? Task.FromResult<Category?>(null)
: Category.Get(CategoryId, ct);
public Task<List<TodoItem>> GetItems(CancellationToken ct = default) =>
TodoItem.Query(i => i.TodoId == Id, ct);
}
Automatic Timestamps
Use [Timestamp] to auto-manage creation and modification times:
public class Order : Entity<Order>
{
public string CustomerId { get; set; } = "";
public decimal Total { get; set; }
[Timestamp] // Set once on first save
public DateTimeOffset CreatedAt { get; set; }
[Timestamp(OnSave = true)] // Updated on every save
public DateTimeOffset UpdatedAt { get; set; }
}
No manual assignment needed — the framework's lifecycle hooks handle it via compiled expression trees for zero-overhead hot-path performance.
When This Skill Applies
- ✅ Designing domain models
- ✅ Complex entity relationships
- ✅ Business logic encapsulation
- ✅ Data validation patterns
- ✅ Soft deletes and audit trails
- ✅ Entity lifecycle management
Reference Documentation
- Full Guide:
docs/guides/data-modeling.md - Entity Patterns:
docs/examples/entity-pattern-recipes.md - Sample:
samples/S1.Web/(Relationship patterns)