| name | add-domain-service |
| description | This skill should be used when the user requests adding business logic, a domain service, an application service, or any logic that should live in the Domain or Application layer of a DDD project. Triggers on phrases like "์๋น์ค ์ถ๊ฐ", "๋๋ฉ์ธ ๋ก์ง", "๋น์ฆ๋์ค ๋ก์ง", "use case ์ถ๊ฐ", "add service", "add use case". Generates Domain interface + implementation + DI registration + unit test scaffold. Do NOT trigger for logic that belongs inside a single Aggregate (add a method to the Aggregate instead), for pure UI/ViewModel work (use add-viewmodel), for infrastructure/config-only changes, or for projects without a Domain/Application layer split (single-project apps, scripts, or utilities with no DDD layering โ forcing a nonexistent layer is wrong; implement directly per the project's structure). |
| argument-hint | <์๋น์ค ์ด๋ฆ ๋๋ ๋ชฉ์ > |
Add Domain Service
DDD ํ๋ก์ ํธ์ ๋น์ฆ๋์ค ๋ก์ง ์๋น์ค(Domain Service ๋๋ Application Service)๋ฅผ ์ถ๊ฐํ๋ค.
๋น์ฆ๋์ค ๋ก์ง์ Domain ๋ ์ด์ด์, ์ค์ผ์คํธ๋ ์ด์
์ Application ๋ ์ด์ด์ ๋๋ค๋ ์์น์ ๊ฐ์ ํ๋ค.
ํธ์ถ ํ๋ฆ
์ด skill์ pjc:implement-task์ Phase I ์์์ ํธ์ถ๋๊ฑฐ๋, ์ฌ์ฉ์๊ฐ ์ง์ /pjc:add-domain-service๋ก ํธ์ถํ ์ ์๋ค.
| ํธ์ถ ๋ฐฉ์ | ํ๋ฆ |
|---|
| implement-task Phase I ์ | plan.md task๊ฐ "Domain Service ์ถ๊ฐ" ๋๋ "UseCase ์ถ๊ฐ" ํจํด์ด๋ฉด ์๋ ํธ์ถ. ์ด skill์ด boilerplate ์์ฑ ํ implement-task์ Phase V๊ฐ ๊ฒ์ฆ์ ์ด์ด๋ฐ์. |
| ์ฌ์ฉ์ ์ง์ ํธ์ถ | plan.md ์์ด ๋จ๋
์ฌ์ฉ ์ $env:CLAUDE_HARNESS_QUICK = '1' ํ์. |
์ด skill์ ์ฑ
์ ๋ฒ์: ์ธํฐํ์ด์ค ์ ์, ๊ตฌํ skeleton, DI ๋ฑ๋ก, ๋จ์ ํ
์คํธ ์ค์บํด๋ + csproj ์์กด ๋ฐฉํฅ ๊ฒ์ฆ.
์ฑ
์ ๋ฒ์ ๋ฐ: ๊ตฌ์ฒด ๋น์ฆ๋์ค ๊ท์น ์์ฑ โ implement-task๊ฐ ๋ด๋น.
์ด skill์ .NET ํ๋ก์ ํธ๋ฅผ ๊ธฐ์ค์ผ๋ก ํ๋ค. ๋ค๋ฅธ ์คํ(Kotlin/Android ๋ฑ)์ AGENTS.md ์ปจ๋ฒค์
์ ์ฐ์ ํ๊ณ , ์๋ ํ
ํ๋ฆฟยท๊ฒ์ฆ์ .NET ๊ธฐ์ค์ ๊ฐ๋
์ ์ฐธ๊ณ ๋ก๋ง ์ฌ์ฉํ๋ค.
์ฌ์ ์กฐ๊ฑด
plan-feature์์ ๋ค์์ด ๊ฒฐ์ ๋์ด ์์ด์ผ ํ๋ค:
- ์๋น์ค ์ด๋ฆ๊ณผ ์ฑ
์ (ํ ๋ฌธ์ฅ)
- ๋ ์ด์ด (Domain Service vs Application Service)
- ์ธํฐํ์ด์ค๋ช
ยท๊ตฌํ๋ช
ยท๋ค์์คํ์ด์ค
- ์์กดํ๋ ๋ค๋ฅธ ์๋น์ค/๋ฆฌํฌ์งํ ๋ฆฌ
- ํธ๋์ญ์
๊ฒฝ๊ณ (์๋ค๋ฉด)
- ๋๋ฉ์ธ ์ด๋ฒคํธ ๋ฐํ ์ฌ๋ถ
๋ฏธ์ ํญ๋ชฉ์ด ์์ผ๋ฉด plan-feature๋ก ๋ณต๊ท.
Domain vs Application โ ์ด๋์ ๋ ๊ฒ์ธ๊ฐ
| ์ผ์ด์ค | ์์น |
|---|
| ์์ ๋น์ฆ๋์ค ๊ท์น (์: ๊ฐ๊ฒฉ ๊ณ์ฐ, ์ ์ฑ
ํ์ ) | Domain Service |
| ์ฌ๋ฌ Aggregate์ ๊ฑธ์น ๊ท์น (Aggregate ์์ ๋ชป ๋ฃ์) | Domain Service |
| ์ธ๋ถ ์์คํ
ํธ์ถ, DB ํธ๋์ญ์
, ๋ฉ์์ง ์ค์ผ์คํธ๋ ์ด์
| Application Service |
| ๋จ์ CRUD + UI ํธ์ถ | Application Service |
| ๋จ์ผ Aggregate ๋ด๋ถ ๋ก์ง | Aggregate์ ๋ฉ์๋๋ก ์ถ๊ฐ (์๋น์ค X) |
ํ๋จ ๊ธฐ์ค์ด ๋ชจํธํ๋ฉด ์ฌ์ฉ์์๊ฒ ํ ๋ฒ ์ง๋ฌธ.
์ ๋ ๊ท์น
- AGENTS.md > ์ด skill. ํ๋ก์ ํธ๊ฐ ๋ค๋ฅธ ์ํคํ
์ฒ(Clean, Hexagonal, Vertical Slice ๋ฑ)๋ฅผ ๋ช
์ํ๋ค๋ฉด ๊ทธ์ชฝ ์ปจ๋ฒค์
์ ๋ฐ๋ฅธ๋ค.
- Domain ๋ ์ด์ด๋ ์ธํ๋ผ ์์กด 0. EF Core, HttpClient, ํ์ผ IO, ILogger ์ง์ ์ฌ์ฉ ๊ธ์ง. ํ์ํ๋ฉด Domain์์ ์ธํฐํ์ด์ค๋ง ์ ์ํ๊ณ ๊ตฌํ์ Infrastructure์ ๋๋ค. (๊ธ์ง/ํ์ฉ ๋ชฉ๋กยท๊ฒ์ฆยทHalt ์ ๋ณธ์ ์๋ "Domain ์์กด ๋ฐฉํฅ ๊ฒ์ฆ" ์น์
.)
- ๋ถ๋ณ์ฑ ์ฐ์ . ๊ฐ๋ฅํ๋ฉด
record, readonly, immutable collection ์ฌ์ฉ.
- Aggregate ๊ฒฝ๊ณ ์กด์ค. ํ ํธ๋์ญ์
์์ ์ฌ๋ฌ Aggregate๋ฅผ ์์ ํ์ง ์๋๋ค.
- ํ๊ธ ์ฃผ์ / UTF-8 / 1500๋ผ์ธ / ์ง์ ๊ฒ์ฆํ ์ฝ๋๋ง ์ฌ์ฉ.
์คํ ๋จ๊ณ
Step 1. ์ปจํ
์คํธ ํ์
- AGENTS.md์์ ๋ ์ด์ด ๊ตฌ์กฐ ํ์ธ โ Domain/Application ๋ ์ด์ด ๋ถ๋ฆฌ๊ฐ ์์ผ๋ฉด(๋น-DDD: ๋จ์ผ ํ๋ก์ ํธยทCore/Infra๋ง ๋ฑ) ์๋ Halt ์กฐ๊ฑด์ผ๋ก ์ค์ง(์กด์ฌํ์ง ์๋ ๋ ์ด์ด๋ฅผ ๊ฐ์ํ์ง ์๋๋ค).
- ๊ธฐ์กด Domain Service / Application Service ํ ๊ฐ์ฉ ์ฝ์ด ์ปจ๋ฒค์
ํ์
:
- ๋ค์ด๋ฐ (
I<Name>Service, <Name>Handler, <Name>UseCase ๋ฑ)
- ๋ฉ์๋ ์๊ทธ๋์ฒ ํจํด (Result ํ์
vs ์์ธ, async vs ๋๊ธฐ)
- ์๋ฌ ์ฒ๋ฆฌ (์ปค์คํ
์์ธ, Result, OneOf ๋ฑ)
- ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค ์์น
Step 2. Domain Service ์ถ๊ฐ (ํด๋น ์)
์ธํฐํ์ด์ค (Domain ๋ ์ด์ด)
src/<Project>.Domain/Services/I<Name>Service.cs:
namespace <Project>.Domain.Services;
public interface I<Name>Service
{
<ReturnType> <MethodName>(<Params>);
}
๊ตฌํ (Domain ๋ ์ด์ด)
src/<Project>.Domain/Services/<Name>Service.cs:
namespace <Project>.Domain.Services;
public sealed class <Name>Service : I<Name>Service
{
private readonly I<Repository> _repo;
public <Name>Service(I<Repository> repo)
{
_repo = repo;
}
public <ReturnType> <MethodName>(<Params>)
{
}
}
Step 3. Application Service ์ถ๊ฐ (ํด๋น ์)
src/<Project>.Application/UseCases/<Name>/<Name>Handler.cs
(๋๋ ํ๋ก์ ํธ ์ปจ๋ฒค์
์ ๋ง๊ฒ):
using <Project>.Domain.Services;
namespace <Project>.Application.UseCases.<Name>;
public sealed record <Name>Request();
public sealed record <Name>Response();
public sealed class <Name>Handler
{
private readonly I<Name>Service _domainService;
private readonly IUnitOfWork _uow;
private readonly ILogger<<Name>Handler> _logger;
public <Name>Handler(
I<Name>Service domainService,
IUnitOfWork uow,
ILogger<<Name>Handler> logger)
{
_domainService = domainService;
_uow = uow;
_logger = logger;
}
public async Task<<Name>Response> HandleAsync(
<Name>Request request,
CancellationToken ct = default)
{
await _uow.SaveChangesAsync(ct);
return new <Name>Response();
}
}
Step 4. DI ๋ฑ๋ก
src/<Project>.Domain/DependencyInjection.cs ๋๋ ๋๋ฑ ์์น:
public static IServiceCollection AddDomainServices(this IServiceCollection services)
{
services.AddScoped<I<Name>Service, <Name>Service>();
return services;
}
src/<Project>.Application/DependencyInjection.cs:
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
services.AddScoped<<Name>Handler>();
return services;
}
์ง์
์ (Program.cs / App.xaml.cs):
services.AddDomainServices();
services.AddApplicationServices();
์๋ช
: ๋ณดํต Scoped. ์ํ ์๋ ์์ ํจ์ํ์ด๋ฉด Transient ๊ฐ๋ฅ. Singleton์ ๋์์ฑ ์ํ.
Step 5. ๋จ์ ํ
์คํธ (Domain ์ฐ์ )
tests/<Project>.Domain.Tests/Services/<Name>ServiceTests.cs:
using NSubstitute;
using Xunit;
namespace <Project>.Domain.Tests.Services;
public class <Name>ServiceTests
{
private readonly I<Repository> _repo = Substitute.For<I<Repository>>();
private <Name>Service CreateSut() => new(_repo);
[Fact]
public void <MethodName>_์ ์์
๋ ฅ_๊ธฐ๋๊ฒฐ๊ณผ๋ฅผ๋ฐํํ๋ค()
{
var sut = CreateSut();
var result = sut.<MethodName>();
Assert.<Expected>(result);
}
[Fact]
public void <MethodName>_๊ท์น์๋ฐ์
๋ ฅ_์์ธ๋ฅผ๋์ง๋ค()
{
var sut = CreateSut();
Assert.Throws<<DomainException>>(() =>
sut.<MethodName>());
}
}
Step 6. ๊ฒ์ฆ
Domain ์์กด ๋ฐฉํฅ ๊ฒ์ฆ
src/<Project>.Domain/<Project>.Domain.csproj์ ๋ค์์ด ์์ด์ผ ํ๋ค:
<PackageReference Include="Microsoft.EntityFrameworkCore..." />
<PackageReference Include="Microsoft.AspNetCore..." />
<PackageReference Include="System.Net.Http..." />
<PackageReference Include="Microsoft.Extensions.Logging" />
<ProjectReference Include="...Infrastructure..." />
<ProjectReference Include="...Application..." />
<ProjectReference Include="...UI..." />
ํ์ฉ:
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
์๋ฐ์ด ๋ฐ๊ฒฌ๋๋ฉด ์ฆ์ Haltํ๊ณ ์ฌ์ฉ์์๊ฒ ๋ณด๊ณ .
์ํฐํจํด (๊ธ์ง)
| ์ํฐํจํด | ์ฌ๋ฐ๋ฅธ ํ๋ |
|---|
Domain์์ DbContext ์ง์ ์ฌ์ฉ | IRepository ์ธํฐํ์ด์ค๋ก ์ถ์ํ |
Domain์์ HttpClient ์ฌ์ฉ | Application ๋ ์ด์ด๋ก ์ด๋ |
Domain์์ DateTime.Now ์ง์ ํธ์ถ | IClock / TimeProvider ์ถ์ํ |
| Domain Service๊ฐ ์ฌ๋ฌ Aggregate๋ฅผ ํธ๋์ญ์
์ผ๋ก ์์ | Application ๋ ์ด์ด์์ ์กฐ์ |
์ ์ ์ํ (static ํ๋๋ก ์บ์) | DI Singleton ๋๋ ์ธ๋ถ ์บ์ |
| Anemic Domain (์๋น์ค๊ฐ ๋ชจ๋ ๋ก์ง, Entity๋ ๋ฐ์ดํฐ๋ง) | Aggregate์ ๋ฉ์๋ ์ถ๊ฐ |
| Repository๋ฅผ ViewModel์์ ์ง์ ํธ์ถ | Application Service ๊ฒฝ์ |
Service๋ช
์ Manager, Helper, Util | ์ฑ
์ ๊ธฐ๋ฐ ๋ช
์ฌ (Calculator, Validator, Policy ๋ฑ) |
| Result type๊ณผ ์์ธ๋ฅผ ํผ์ฉ | ํ๋ก์ ํธ ์ปจ๋ฒค์
ํ๋๋ก ํต์ผ |
| ์๋ฌธ XML doc ์ฃผ์ | ํ๊ธ |
Halt ์กฐ๊ฑด
- Domain ๋ ์ด์ด csproj์ ์ธํ๋ผ ์์กด์ฑ ๋ฐ๊ฒฌ (โ "Domain ์์กด ๋ฐฉํฅ ๊ฒ์ฆ" ์น์
๋๋ก ์ฆ์ Halt โ ๊ทธ ์น์
์ด ๊ธ์ง/ํ์ฉยทHalt ์ ๋ณธ)
- ๊ธฐ์กด ์๋น์ค๋ค์ด ์ผ๊ด๋ ํจํด์ ๋ฐ๋ฅด์ง ์์ (ํผ๋ โ ์ฌ์ฉ์ ํ์ธ ํ์)
- Aggregate ๊ฒฝ๊ณ๊ฐ ๋ถ๋ช
ํ
- ์์ํ๋ฅผ ์๋ฐํ๋ Application Service์ธ๋ฐ ํธ๋์ญ์
๊ฒฝ๊ณ๊ฐ plan.md์ ๋ช
์๋์ง ์์ (์์ ๋๋ฉ์ธ ๊ณ์ฐยท์กฐํ ๋ฑ ์์ํ๊ฐ ์๋ ์๋น์ค๋ ์ด Halt ๋์์ด ์๋๋ค โ ํธ๋์ญ์
์ด ํ์ ์์ผ๋ฏ๋ก)
- ๋์ผ ์ด๋ฆ์ ์๋น์ค๊ฐ ์ด๋ฏธ ์กด์ฌ
- ํ๋ก์ ํธ์ Domain/Application ๋ ์ด์ด๊ฐ ์์(๋น-DDD) โ ์กด์ฌํ์ง ์๋
src/<Project>.Domain/ยท.Application/ ๊ตฌ์กฐ๋ฅผ ์๋ก ๊ฐ์ํ์ง ๋ง๊ณ , ์ด๋์ ๋์ง(๋๋ ๋จ์ ์๋น์ค ํด๋์ค๋ก ๋์ง)๋ฅผ ์ฌ์ฉ์์๊ฒ ํ์ธ