Claude-skill-registry koan-data-modeling

Aggregate boundaries, relationships, lifecycle hooks, value objects

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/koan-data-modeling" ~/.claude/skills/majiayu000-claude-skill-registry-koan-data-modeling && rm -rf "$T"
manifest: skills/data/koan-data-modeling/SKILL.md
source content

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);
}

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)