Agent skill

domain-sidnovais-healthcore

Implement a complete domain feature in an HC.LIS module — business rules, domain events, aggregate changes, and unit tests following DDD conventions.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/domain-sidnovais-healthcore

SKILL.md

/domain

You are a domain modeling expert for the HC.LIS Modular Monolith. Your job is to implement a complete domain feature — from business rules and domain events to entity/aggregate changes and unit tests — following every HC.LIS convention exactly.

Invocation format

/domain [ModuleName?] <requirement description>

Examples:

  • /domain TestOrders accept an exam
  • /domain cancel an order item with a reason

Phase 1 — Resolve module

Extract ModuleName from the arguments (first PascalCase word).

  • If not provided: list the subdirectories of src/HC.LIS/HC.LIS.Modules/ and ask the user to pick one.
  • If provided but the directory src/HC.LIS/HC.LIS.Modules/{ModuleName}/ does not exist: stop with a clear error — do not guess or create the module.

Phase 2 — Exploration (read-only, before writing any code)

Read these files to understand the module's domain model:

  1. List the directory tree of src/HC.LIS/HC.LIS.Modules/{ModuleName}/Domain/ to identify aggregates, entities, existing events, and existing rules.
  2. Read the aggregate root file (e.g., Domain/Orders/Order.cs) — understand existing command methods, Apply() dispatch, and When() handlers.
  3. Read relevant child entity files (e.g., Domain/Orders/OrderItem.cs) — understand CheckRule() patterns.
  4. Read all existing domain event files in Domain/{Aggregate}/Events/ to understand naming and structure.
  5. Read a sample business rule file from Domain/{Aggregate}/Rules/ to understand the exception+rule pattern.
  6. Read the unit test file(s) under Tests/UnitTests/ to understand AssertPublishedDomainEvent<T>() and AssertBrokenRule<TRule>() usage.
  7. Read any factory file (e.g., Tests/UnitTests/Orders/OrderFactory.cs) and the sample data file (e.g., OrderSampleData.cs) to understand test setup.

Do NOT skip any of these reads — the generated code must fit into existing code, not invent new patterns.


Phase 3 — Clarify if needed

Before writing any code, check if any of these are ambiguous:

  • Which aggregate or entity owns the behavior?
  • What state transitions are involved? (e.g., which statuses block the action?)
  • What parameters does the command take?
  • Are there related rules that guard the transition?

If uncertain, ask the user one focused question. Do not generate speculative code.


Phase 4 — Generate all artifacts

Implement all of the following in one pass. Do not stop at one file.

4a. Domain event

File: src/HC.LIS/HC.LIS.Modules/{ModuleName}/Domain/{Aggregate}/Events/{Aggregate}{Action}DomainEvent.cs

Rules:

  • Inherits DomainEvent from HC.Core.Domain
  • Uses primary constructor syntax (C# 12+)
  • Properties initialized inline from constructor parameters
  • Store primitives only (Guid, string, DateTime, bool, int, decimal) — never value objects or entities
  • File-scoped namespace (no outer {} block)

Pattern:

csharp
using System;
using HC.Core.Domain;

namespace HC.LIS.Modules.{ModuleName}.Domain.{Aggregate}.Events;

public class {Aggregate}{Action}DomainEvent(
    Guid {aggregateId},
    // ... other primitive params
) : DomainEvent
{
    public Guid {AggregateId} { get; } = {aggregateId};
    // ... other properties
}

4b. Business rule(s)

File: src/HC.LIS/HC.LIS.Modules/{ModuleName}/Domain/{Aggregate}/Rules/Cannot{X}Rule.cs

One file per rule. The exception class and the rule class live in the same file — exception first, rule second.

Exception class rules:

  • Name: {Action}{X}Exception (e.g., AcceptOrderItemMoreThanOnceException)
  • Inherits BaseBusinessRuleException
  • Must have exactly 4 overloads: (), (string message), (string message, Exception innerException), (IBusinessRule rule)

Rule class rules:

  • Name: Cannot{X}Rule (e.g., CannotAcceptOrderItemMoreThanOnceRule)
  • Implements IBusinessRule
  • Uses primary constructor; stores args in private readonly fields via body
  • IsBroken() returns a bool expression against the field(s)
  • ThrowException() calls throw new {Exception}(this)
  • Message property returns a human-readable string

Pattern:

csharp
using HC.Core.Domain;

namespace HC.LIS.Modules.{ModuleName}.Domain.{Aggregate}.Rules;

public class {Action}{X}Exception : BaseBusinessRuleException
{
    public {Action}{X}Exception() { }
    public {Action}{X}Exception(string message) : base(message) { }
    public {Action}{X}Exception(string message, System.Exception innerException) : base(message, innerException) { }
    public {Action}{X}Exception(IBusinessRule rule) : base(rule) { }
}

public class Cannot{X}Rule(
    {StateType} actualState
) : IBusinessRule
{
    private readonly {StateType} _actualState = actualState;
    public bool IsBroken() => /* condition */;
    public void ThrowException() => throw new {Action}{X}Exception(this);
    public string Message => "...";
}

4c. Aggregate root — add command method and When() handler

Edit the aggregate root file. Add:

  1. A public command method:

    • Creates a new domain event (primitives only — unwrap value objects)
    • Calls Apply(domainEvent)
    • Calls AddDomainEvent(domainEvent)
  2. A private When({EventType} domainEvent) handler:

    • Delegates to the child entity if the entity owns the behavior, or updates aggregate state directly

If the aggregate delegates to an entity:

csharp
// In Order.cs (aggregate)
public void {Action}Exam(OrderItemId orderItemId, /* params */)
{
    {Aggregate}{Action}DomainEvent ev = new(orderItemId.Value, /* params */);
    Apply(ev);
    AddDomainEvent(ev);
}

private void When({Aggregate}{Action}DomainEvent domainEvent)
    => _items.Single(i => i.OrderItemId.Value == domainEvent.OrderItemId).{Action}(domainEvent);

4d. Entity — add the behavior method

Edit the entity file. Add a method that:

  1. Calls CheckRule(new Cannot{X}Rule(...)) for each guard (use current entity state fields)
  2. Calls Apply(domainEvent) after all checks pass

Then add the private When({EventType} domainEvent) handler that updates the entity's state fields.

Pattern:

csharp
internal void {Action}({EventType} domainEvent)
{
    CheckRule(new Cannot{X}Rule(_status));
    // ... additional rules
    Apply(domainEvent);
}

private void When({EventType} domainEvent)
{
    _status = {EntityStatus}.{NewStatus};
    _{actionedAt} = domainEvent.{ActionedAt};
}

4e. Unit tests

Edit the existing unit test file (e.g., Tests/UnitTests/Orders/OrderTests.cs). Add:

  1. Happy path test[Fact], PascalCase name, no underscores:

    • Arrange: use existing _sut and OrderSampleData
    • Act: call the new aggregate method
    • Assert: AssertPublishedDomainEvent<{EventType}>(_sut) and verify each property
  2. Broken rule test — one [Fact] per business rule:

    • Arrange/Act: set up state that triggers the rule
    • Assert: AssertBrokenRule<Cannot{X}Rule>(action)

Test naming conventions (strictly enforced):

  • Happy path: {Action}ExamIsSuccessful
  • Broken rule: {Action}ExamShouldBroke{RuleName}When{Condition}
  • PascalCase only — no underscores (CA1707 will fail the build)

Test pattern:

csharp
[Fact]
public void {Action}ExamIsSuccessful()
{
    DateTime actionedAt = SystemClock.Now;
    _sut.{Action}Exam(new OrderItemId(OrderSampleData.OrderItemId), actionedAt);
    {EventType} ev = AssertPublishedDomainEvent<{EventType}>(_sut);
    ev.OrderItemId.Should().Be(OrderSampleData.OrderItemId);
    ev.{ActionedAt}.Should().Be(actionedAt);
}

[Fact]
public void {Action}ExamShouldBroke{RuleName}When{Condition}()
{
    // put entity into required state first
    _sut.{Action}Exam(new OrderItemId(OrderSampleData.OrderItemId), SystemClock.Now);
    void action()
    {
        _sut.{Action}Exam(new OrderItemId(OrderSampleData.OrderItemId), SystemClock.Now);
    }
    AssertBrokenRule<Cannot{X}Rule>(action);
}

Phase 5 — Verify

After writing all files, run:

bash
dotnet build

Fix any compilation errors before reporting success. Then remind the user to run the unit tests:

bash
dotnet test src/HC.LIS/HC.LIS.Modules/{ModuleName}/Tests/UnitTests/...

Analyzer rules you must not violate (TreatWarningsAsErrors=true)

Rule What to do
CA1002 Never expose List<T> — use IReadOnlyCollection<T>
CA1707 No underscores in public/test member names — PascalCase only
CA1716 Avoid reserved keywords in namespaces
CA2007 Use .ConfigureAwait(false) on all awaited tasks in infra/lib code
CA2201 Use specific exception types, never new Exception()

Code style (non-negotiable)

  • File-scoped namespaces — no outer {} blocks
  • 4-space indentation
  • Private fields: _camelCase; static fields: s_camelCase; constants: PascalCase
  • Primary constructors where appropriate
  • Domain events carry primitives only — unwrap value objects at the call site

Critical reference files

When in doubt, read these:

File What it shows
src/HC.LIS/HC.LIS.Modules/TestOrders/Domain/Orders/Order.cs Aggregate command + Apply + When pattern
src/HC.LIS/HC.LIS.Modules/TestOrders/Domain/Orders/OrderItem.cs Entity CheckRule + Apply pattern
src/HC.LIS/HC.LIS.Modules/TestOrders/Domain/Orders/Events/OrderItemAcceptedDomainEvent.cs Event structure
src/HC.LIS/HC.LIS.Modules/TestOrders/Domain/Orders/Rules/CannotAcceptOrderItemMoreThanOnceRule.cs Exception + rule co-location
src/HC.Core/Domain/Entity.cs CheckRule(), AddEvent(), Events, ClearEvents()
src/HC.Core/Domain/EventSourcing/AggregateRoot.cs AddDomainEvent(), Apply(), Load()
src/HC.Core/Domain/IBusinessRule.cs Rule interface
src/HC.Core/Domain/BaseBusinessRuleException.cs Exception base
src/HC.Core/Tests/UnitTests/TestBase.cs AssertPublishedDomainEvent<T>(), AssertBrokenRule<TRule>()

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results