ronnythedev/dotnet-clean-architecture-skills/04-dotnet-domain-entity-generator

Claude Code and agent skill by ronnythedev/dotnet-clean-architecture-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.

Install

npx vskill@latest install ronnythedev/dotnet-clean-architecture-skills/04-dotnet-domain-entity-generator

SKILL.md

Domain Entity Generator

Overview

This skill generates Domain Entities following Domain-Driven Design (DDD) principles:

  • Encapsulation - Private setters, controlled modification
  • Factory Methods - Static Create() methods with validation
  • Domain Events - State changes raise events
  • Rich Domain Model - Behavior lives in the entity, not services
  • Invariant Protection - Entity always in valid state

Quick Reference

ConceptPurposeExample
Aggregate RootEntry point for aggregateOrganization, User
Child EntityPart of aggregate, no own identity outsideOrderItem, AssessmentDetail
Value ObjectImmutable, no identityEmail, Money, Address
Domain EventSignal state changeUserCreatedDomainEvent

Entity Structure

/Domain/{Aggregate}/
├── {Entity}.cs # Main entity
├── {Entity}Errors.cs # Typed errors
├── I{Entity}Repository.cs # Repository interface
├── ValueObjects/
│ ├── {ValueObject}.cs
│ └── ...
└── Events/
├── {Entity}CreatedDomainEvent.cs
├── {Entity}UpdatedDomainEvent.cs
└── ...

Template: Aggregate Root Entity

csharp
// src/{name}.domain/{Aggregate}/{Entity}.cs
using {name}.domain.abstractions;
using {name}.domain.{aggregate}.events;
namespace {name}.domain.{aggregate};
public sealed class {Entity} : Entity
{
// ═══════════════════════════════════════════════════════════════
// PRIVATE COLLECTIONS (encapsulated)
// ═══════════════════════════════════════════════════════════════
private readonly List<{ChildEntity}> _{childEntities} = new();
// ═══════════════════════════════════════════════════════════════
// PROPERTIES (private setters)
// ═══════════════════════════════════════════════════════════════
public string Name { get; private set; }
public string? Description { get; private set; }
public bool IsActive { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime UpdatedAt { get; private set; }
// Navigation property (read-only collection)
public IReadOnlyCollection<{ChildEntity}> {ChildEntities} => _{childEntities}.AsReadOnly();
// ═══════════════════════════════════════════════════════════════
// CONSTRUCTORS
// ═══════════════════════════════════════════════════════════════
// Private constructor for EF Core
private {Entity}() { }
// Private constructor for factory method
private {Entity}(
Guid id,
string name,
string? description,
DateTime createdAt)
: base(id)
{
Name = name;
Description = description;
IsActive = true;
CreatedAt = createdAt;
UpdatedAt = createdAt;
}
// ═══════════════════════════════════════════════════════════════
// FACTORY METHODS
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Creates a new {Entity} with validation
/// </summary>
public static Result<{Entity}> Create(
string name,
string? description,
DateTime createdAt)
{
// Validate invariants
if (string.IsNullOrWhiteSpace(name))
{
return Result.Failure<{Entity}>({Entity}Errors.NameIsRequired);
}
if (name.Length > 100)
{
return Result.Failure<{Entity}>({Entity}Errors.NameTooLong);
}
var {entity} = new {Entity}(
Guid.NewGuid(),
name,
description,
createdAt);
// Raise domain event
{entity}.RaiseDomainEvent(new {Entity}CreatedDomainEvent({entity}.Id));
return {entity};
}
// ═══════════════════════════════════════════════════════════════
// DOMAIN METHODS
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Updates the {Entity} properties
/// </summary>
public Result Update(
string name,
string? description,
DateTime updatedAt)
{
if (string.IsNullOrWhiteSpace(name))
{
return Result.Failure({Entity}Errors.NameIsRequired);
}
if (name.Length > 100)
{
return Result.Failure({Entity}Errors.NameTooLong);
}
Name = name;
Description = description;
UpdatedAt = updatedAt;
RaiseDomainEvent(new {Entity}UpdatedDomainEvent(Id));
return Result.Success();
}
/// <summary>
/// Deactivates the {Entity}
/// </summary>
public Result Deactivate(DateTime updatedAt)
{
if (!IsActive)
{
return Result.Failure({Entity}Errors.AlreadyDeactivated);
}
IsActive = false;
UpdatedAt = updatedAt;
RaiseDomainEvent(new {Entity}DeactivatedDomainEvent(Id));
return Result.Success();
}
/// <summary>
/// Reactivates the {Entity}
/// </summary>
public Result Activate(DateTime updatedAt)
{
if (IsActive)
{
return Result.Failure({Entity}Errors.AlreadyActive);
}
IsActive = true;
UpdatedAt = updatedAt;
return Result.Success();
}
// ═══════════════════════════════════════════════════════════════
// CHILD ENTITY MANAGEMENT
// ═══════════════════════════════════════════════════════════════
/// <summary>
/// Adds a child entity to this aggregate
/// </summary>
public Result Add{ChildEntity}({ChildEntity} {childEntity})
{
if ({childEntity} is null)
{
return Result.Failure({Entity}Errors.Child{ChildEntity}Required);
}
if (_{childEntities}.Any(c => c.Name == {childEntity}.Name))
{
return Result.Failure({Entity}Errors.Duplicate{ChildEntity}Name);
}
_{childEntities}.Add({childEntity});
RaiseDomainEvent(new {ChildEntity}AddedDomainEvent(Id, {childEntity}.Id));
return Result.Success();
}
/// <summary>
/// Removes a child entity from this aggregate
/// </summary>
public Result Remove{ChildEntity}(Guid {childEntity}Id)
{
var {childEntity} = _{childEntities}.FirstOrDefault(c => c.Id == {childEntity}Id);
if ({childEntity} is null)
{
return Result.Failure({Entity}Errors.{ChildEntity}NotFound);
}
_{childEntities}.Remove({childEntity});
return Result.Success();
}
// ═══════════════════════════════════════════════════════════════
// QUERY METHODS
// ═══════════════════════════════════════════════════════════════
public bool HasActiveChildren() => _{childEntities}.Any(c => c.IsActive);
public {ChildEntity}? GetChildById(Guid childId) =>
_{childEntities}.FirstOrDefault(c => c.Id == childId);
}

Template: Child Entity (Part of Aggregate)

csharp
// src/{name}.domain/{Aggregate}/{ChildEntity}.cs
using {name}.domain.abstractions;
namespace {name}.domain.{aggregate};
public sealed class {ChildEntity} : Entity
{
// ═══════════════════════════════════════════════════════════════
// PROPERTIES
// ═══════════════════════════════════════════════════════════════
public Guid {Parent}Id { get; private set; }
public string Name { get; private set; }
public string? Description { get; private set; }
public int SortOrder { get; private set; }
public bool IsActive { get; private set; }
public DateTime CreatedAt { get; private set; }
public DateTime UpdatedAt { get; private set; }
// Navigation property
public {Parent} {Parent} { get; private set; } = null!;
// ═══════════════════════════════════════════════════════════════
// CONSTRUCTORS
// ═══════════════════════════════════════════════════════════════
private {ChildEntity}() { } // EF Core
private {ChildEntity}(
Guid id,
Guid {parent}Id,
string name,
string? description,
int sortOrder,
DateTime createdAt)
: base(id)
{
{Parent}Id = {parent}Id;
Name = name;
Description = description;
SortOrder = sortOrder;
IsActive = true;
CreatedAt = createdAt;
UpdatedAt = createdAt;
}
// ═══════════════════════════════════════════════════════════════
// FACTORY METHOD
// ═══════════════════════════════════════════════════════════════
public static {ChildEntity} Create(
Guid {parent}Id,
string name,
string? description,
int sortOrder,
DateTime createdAt)
{
return new {ChildEntity}(
Guid.NewGuid(),
{parent}Id,
name,
description,
sortOrder,
createdAt);
}
// ═══════════════════════════════════════════════════════════════
// DOMAIN METHODS
// ═══════════════════════════════════════════════════════════════
public void Update(
string name,
string? description,
int sortOrder,
DateTime updatedAt)
{
Name = name;
Description = description;
SortOrder = sortOrder;
UpdatedAt = updatedAt;
}
public void Deactivate(DateTime updatedAt)
{
IsActive = false;
UpdatedAt = updatedAt;
}
}

Template: Value Object

csharp
// src/{name}.domain/{Aggregate}/ValueObjects/Email.cs
namespace {name}.domain.{aggregate}.valueobjects;
public sealed record Email
{
public string Value { get; }
private Email(string value)
{
Value = value;
}
public static Result<Email> Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
{
return Result.Failure<Email>(EmailErrors.Empty);
}
email = email.Trim().ToLowerInvariant();
if (email.Length > 255)
{
return Result.Failure<Email>(EmailErrors.TooLong);
}
if (!IsValidFormat(email))
{
return Result.Failure<Email>(EmailErrors.InvalidFormat);
}
return new Email(email);
}
private static bool IsValidFormat(string email)
{
// Simple email validation
var atIndex = email.IndexOf('@');
var dotIndex = email.LastIndexOf('.');
return atIndex > 0
&& dotIndex > atIndex + 1
&& dotIndex < email.Length - 1;
}
public override string ToString() => Value;
// Implicit conversion for convenience
public static implicit operator string(Email email) => email.Value;
}
public static class EmailErrors
{
public static readonly Error Empty = new("Email.Empty", "Email cannot be empty");
public static readonly Error TooLong = new("Email.TooLong", "Email cannot exceed 255 characters");
public static readonly Error InvalidFormat = new("Email.InvalidFormat", "Email format is invalid");
}
More Value Object Examples
csharp
// Money Value Object
public sealed record Money
{
public decimal Amount { get; }
public string Currency { get; }
private Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public static Result<Money> Create(decimal amount, string currency = "USD")
{
if (amount < 0)
return Result.Failure<Money>(MoneyErrors.NegativeAmount);
if (string.IsNullOrWhiteSpace(currency) || currency.Length != 3)
return Result.Failure<Money>(MoneyErrors.InvalidCurrency);
return new Money(Math.Round(amount, 2), currency.ToUpperInvariant());
}
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new InvalidOperationException("Cannot add different currencies");

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
64
Repository forks
11
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/ronnythedev/dotnet-clean-architecture-skills/04-dotnet-domain-entity-generator/badge)](https://verified-skill.com/skills/ronnythedev/dotnet-clean-architecture-skills/04-dotnet-domain-entity-generator)

Something wrong with this skill or its page? Tell us.