LogoSkills

feature:domain

Clean Architecture Domain Layer generation

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/feature:domain
Aliases/domain, /domain:create
Categorykobic-workflow
Complexitymoderate
MCP Serversserena, context7

/feature:domain โ€” ๊ธฐ๋Šฅ์˜ "ํ•ต์‹ฌ ๊ทœ์น™์ธต" ์ž๋™ ์ƒ์„ฑ#

ํ•œ๋งˆ๋””๋กœ#

์•ฑ ๊ธฐ๋Šฅ์˜ ๊ฐ€์žฅ ์•ˆ์ชฝ ๋ผˆ๋Œ€(ํ•ต์‹ฌ ๊ทœ์น™ยท๋ฐ์ดํ„ฐ ๋ชจ์–‘ยท๋™์ž‘ ์ •์˜) ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๊ฑด๋ฌผ๋กœ ์น˜๋ฉด ์ธํ…Œ๋ฆฌ์–ด๋‚˜ ์™ธ๊ด€ ์ „์— ๋จผ์ € ์„ธ์šฐ๋Š” ์ฒ ๊ณจ ๊ตฌ์กฐ์™€ ์„ค๊ณ„ ๋„๋ฉด์— ํ•ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์ด์—์š”.

๋ˆ„๊ฐ€ยท์–ธ์ œ ์“ฐ๋‚˜์š”#

  • ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด์„œ ๊ทธ ๊ธฐ๋Šฅ์˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ํ•ต์‹ฌ ๋™์ž‘ ๊ทœ์น™์„ ์ •์˜ํ•ด์•ผ ํ•  ๋•Œ
  • ๊ฒŒ์‹œ๊ธ€(Post), ๋ฉ”์‹œ์ง€(Message)์ฒ˜๋Ÿผ ๋‹ค๋ฃฐ ๋Œ€์ƒ(Entity) ๊ณผ "๋ชฉ๋ก ์กฐํšŒยท์ƒ์„ฑยท์ˆ˜์ •ยท์‚ญ์ œ" ๊ฐ™์€ ๋™์ž‘(UseCase) ์ด ํ•„์š”ํ•  ๋•Œ
  • ์ „์ฒด ๊ธฐ๋Šฅ์„ ํ•œ ๋ฒˆ์— ๋งŒ๋“œ๋Š” /feature:create ์ž‘์—…์˜ 3๋ฒˆ์งธ ๋‹จ๊ณ„๋กœ ์ž๋™ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค

๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”#

๊ธฐ๋Šฅ์˜ ๋„๋ฉ”์ธ(ํ•ต์‹ฌ ๊ทœ์น™) ํด๋” ์•ˆ์— ํ•„์š”ํ•œ ํŒŒ์ผ๋“ค์„ ํ•œ ๋ฒˆ์— ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

  • Entity โ€” ๋‹ค๋ฃฐ ๋Œ€์ƒ์˜ ๋ฐ์ดํ„ฐ ๋ชจ์–‘ ์ •์˜ (์˜ˆ: post.dart)
  • Repository Interface โ€” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ €์žฅํ•˜๋Š” "์•ฝ์†(๊ทœ๊ฒฉ)" ์ •์˜ (์˜ˆ: i_community_repository.dart)
  • UseCase โ€” "๋ชฉ๋ก ์กฐํšŒยท๋‹จ๊ฑด ์กฐํšŒยท์ƒ์„ฑยท์ˆ˜์ •ยท์‚ญ์ œ" ๊ฐ™์€ ๊ฐœ๋ณ„ ๋™์ž‘ (์˜ˆ: get_posts_usecase.dart)
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ โ€” ๊ฐ ๋™์ž‘์ด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ์ž๋™ ํ…Œ์ŠคํŠธ
  • ๊ทธ ์™ธ ์‹คํŒจ ๋ฉ”์‹œ์ง€ยท์˜ˆ์™ธ ์ฒ˜๋ฆฌ์šฉ ํŒŒ์ผ๊ณผ ๋ฌถ์Œ export ํŒŒ์ผ๊นŒ์ง€ ํ•จ๊ป˜ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค

์–ด๋–ป๊ฒŒ ์“ฐ๋‚˜์š”#

# ๊ฒŒ์‹œ๊ธ€(Post) ๋„๋ฉ”์ธ ๋งŒ๋“ค๊ธฐ
/feature:domain community Post --location application

# ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€(Message) ๋„๋ฉ”์ธ์„, ์›ํ•˜๋Š” ๋™์ž‘๋งŒ ๊ณจ๋ผ์„œ ๋งŒ๋“ค๊ธฐ
/feature:domain chat Message --location application
  --usecases  " getMessages, sendMessage, markAsRead "
  • ์ฒซ ๋ฒˆ์งธ ๊ฐ’์€ ๊ธฐ๋Šฅ ์ด๋ฆ„(community, chat ๋“ฑ), ๋‘ ๋ฒˆ์งธ ๊ฐ’์€ ๋Œ€์ƒ ์ด๋ฆ„(Post, Message ๋“ฑ)์ž…๋‹ˆ๋‹ค.
  • --location ์œผ๋กœ ์–ด๋””์— ๋งŒ๋“ค์ง€(application/common/console) ์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ธฐ๋ณธ๊ฐ’์€ application ์ž…๋‹ˆ๋‹ค.
  • --usecases ๋กœ ํ•„์š”ํ•œ ๋™์ž‘๋งŒ ์ฝค๋งˆ๋กœ ๋‚˜์—ดํ•ด ๊ณจ๋ผ์„œ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ˆ์—์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์ง€๋‚˜์š”#

  1. ๊ธฐ์กด ํŒจํ„ด ์‚ดํŽด๋ณด๊ธฐ โ€” ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ ๋น„์Šทํ•œ ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•ด ๊ฐ™์€ ์Šคํƒ€์ผ๋กœ ๋งž์ถฅ๋‹ˆ๋‹ค.
  2. ๋ฐ์ดํ„ฐ ๋ชจ์–‘(Entity) ๋งŒ๋“ค๊ธฐ โ€” ๋‹ค๋ฃฐ ๋Œ€์ƒ์˜ ํ•ญ๋ชฉ๋“ค(์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์ž‘์„ฑ์ผ ๋“ฑ)์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  3. ๋ฐ์ดํ„ฐ ์•ฝ์†(Repository Interface) ๋งŒ๋“ค๊ธฐ โ€” ์กฐํšŒยท์ƒ์„ฑยท์ˆ˜์ •ยท์‚ญ์ œ ๊ฐ™์€ ๊ทœ๊ฒฉ์„ ์ •ํ•ฉ๋‹ˆ๋‹ค.
  4. ๋™์ž‘(UseCase) ๋งŒ๋“ค๊ธฐ โ€” ๊ฐ ๊ธฐ๋Šฅ ๋™์ž‘์„ ํ•˜๋‚˜์”ฉ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  5. ํ…Œ์ŠคํŠธ ๋งŒ๋“ค๊ธฐ โ€” ๊ฐ ๋™์ž‘์ด ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ํ•จ๊ป˜ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  6. ๋งˆ๋ฌด๋ฆฌ ์ •๋ฆฌ โ€” ์ฝ”๋“œ ์ƒ์„ฑ ๋ช…๋ น์„ ๋Œ๋ ค ์ž๋™ ์ƒ์„ฑ ํŒŒ์ผ๋“ค์„ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ ์ƒ์„ธ ์˜ต์…˜ยท์‹คํ–‰ ๋ช…์„ธ (๊ฐœ๋ฐœ์ž / AI ์—์ด์ „ํŠธ์šฉ)

Triggers#

  • When a new Feature Domain Layer is needed
  • When Entity, Repository Interface, and UseCase generation are needed
  • /feature:create orchestration Step 3

Context Trigger Pattern#

/feature:domain {feature_name} {entity_name} [--options]

Parameters#

ParameterRequiredDescriptionExample
feature_nameโœ…Feature module name (snake_case)community, chat
entity_nameโœ…Entity name (PascalCase)Post, Message
--locationโŒLocationapplication, common, console (default: application)
--usecasesโŒTo generate UseCase"getList, get, create, update, delete"

Behavioral Flow#

1. Existing Pattern Analysis#

Analyze existing Domain Layer patterns using Serena MCP:
- feature/application/community/lib/src/domain/entity/post.dart
- feature/application/community/lib/src/domain/repository/i_community_repository.dart
- feature/application/community/lib/src/domain/usecase/get_posts_usecase.dart

2. Entity Generation#

import 'package:dependencies/dependencies.dart';

/// {์—”ํ‹ฐํ‹ฐ} ํ•œ๊ธ€ ์„ค๋ช…
class {Entity} extends Equatable {
  /// [{Entity}]๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  const {Entity}({
    required this.id,
    required this.title,
    required this.authorId,
    required this.createdAt,
    this.updatedAt,
  });

  final int id;
  final String title;
  final int authorId;
  final DateTime createdAt;
  final DateTime? updatedAt;

  @override
  List<Object?> get props => [id, title, authorId, createdAt, updatedAt];
}

3. Generate Repository Interface#

import 'package:dependencies/dependencies.dart';

/// {Feature} Repository Interface
abstract interface class I{Feature}Repository {
  /// {์—”ํ‹ฐํ‹ฐ} ๋ชฉ๋ก์„ SWR Stream์œผ๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  Stream<Either<Failure, List<{Entity}>>> get{Entity}s({
    {Entity}Category? category,
  });

  /// {์—”ํ‹ฐํ‹ฐ} ๋‹จ๊ฑด ์กฐํšŒ
  Future<Either<Failure, {Entity}>> get{Entity}(int {entity}Id);

  /// {์—”ํ‹ฐํ‹ฐ} ์ƒ์„ฑ
  Future<Either<Failure, {Entity}>> create{Entity}({...});

  /// {์—”ํ‹ฐํ‹ฐ} ์ˆ˜์ •
  Future<Either<Failure, {Entity}>> update{Entity}({...});

  /// {์—”ํ‹ฐํ‹ฐ} ์‚ญ์ œ
  Future<Either<Failure, void>> delete{Entity}(int {entity}Id);
}

4. Generate UseCase (Optional Constructor Injection)#

import 'package:core/core.dart';
import 'package:dependencies/dependencies.dart';

/// {์—”ํ‹ฐํ‹ฐ} ๋ชฉ๋ก ์กฐํšŒ Params
class Get{Entity}sParams {
  const Get{Entity}sParams({
    this.limit = 20,
    this.offset = 0,
    this.category,
  });

  final int limit;
  final int offset;
  final {Entity}Category? category;
}

/// {์—”ํ‹ฐํ‹ฐ} ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” UseCase
class Get{Entity}sUsecase {

  /// [Get{Entity}sUsecase]๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  const Get{Entity}sUsecase([I{Feature}Repository? repo])
      : _repo = repo ?? const {Feature}Repository();

  final I{Feature}Repository _repo;

  Future<Either<Failure, {Entity}ListResult>> call(Get{Entity}sParams params) {
    return _repo.get{Entity}s(
      limit: params.limit,
      offset: params.offset,
      category: params.category,
    );
  }
}

5. Generate UseCase tests#

void main() {
  late Get{Entity}sUsecase usecase;
  late MockI{Feature}Repository mockRepository;

  setUpAll(registerFallbackValues);

  setUp(() {
    mockRepository = MockI{Feature}Repository();
    usecase = Get{Entity}sUsecase(mockRepository);  // ์ง์ ‘ ์ฃผ์ž…
  });

  group('Get{Entity}sUsecase', () {
    test('์„ฑ๊ณต ์‹œ Right({Entity}ListResult) ๋ฐ˜ํ™˜', () async {
      // arrange
      when(() => mockRepository.get{Entity}s(...))
        .thenAnswer((_) async => Right(testResult));

      // act
      final result = await usecase(testParams);

      // assert
      expect(result.isRight(), true);
      verify(() => mockRepository.get{Entity}s(...)).called(1);
    });
  });
}

Output Files#

feature/{location}/{feature_name}/lib/src/domain/
โ”œโ”€โ”€ entity/
โ”‚   โ”œโ”€โ”€ {entity}.dart
โ”‚   โ”œโ”€โ”€ {entity}_list_result.dart
โ”‚   โ””โ”€โ”€ entity.dart           # export
โ”œโ”€โ”€ repository/
โ”‚   โ”œโ”€โ”€ i_{feature}_repository.dart
โ”‚   โ””โ”€โ”€ repository.dart       # export
โ”œโ”€โ”€ usecase/
โ”‚   โ”œโ”€โ”€ get_{entity}s_usecase.dart
โ”‚   โ”œโ”€โ”€ get_{entity}_usecase.dart
โ”‚   โ”œโ”€โ”€ create_{entity}_usecase.dart
โ”‚   โ”œโ”€โ”€ update_{entity}_usecase.dart
โ”‚   โ”œโ”€โ”€ delete_{entity}_usecase.dart
โ”‚   โ””โ”€โ”€ usecase.dart          # export
โ”œโ”€โ”€ failure/
โ”‚   โ””โ”€โ”€ {feature}_failure_messages.dart
โ””โ”€โ”€ exception/
    โ””โ”€โ”€ {feature}_exception.dart

feature/{location}/{feature_name}/test/domain/usecase/
โ”œโ”€โ”€ get_{entity}s_usecase_test.dart
โ”œโ”€โ”€ get_{entity}_usecase_test.dart
โ”œโ”€โ”€ create_{entity}_usecase_test.dart
โ”œโ”€โ”€ update_{entity}_usecase_test.dart
โ””โ”€โ”€ delete_{entity}_usecase_test.dart

Post-Generation Commands#

# Code generation (Freezed ๋“ฑ)
melos exec --scope={feature_name} --  " dart run build_runner build --delete-conflicting-outputs "

MCP Integration#

  • Serena: Existing Domain Layer Pattern Analysis, Symbol search
  • Context7: Clean Architecture, Either Pattern Document reference

Examples#

Create Post Domain#

/feature:domain community Post --location application

Create Chat Message Domain#

/feature:domain chat Message --location application
  --usecases  " getMessages, sendMessage, markAsRead "

Reference Agents#

Reference ${CLAUDE_PLUGIN_ROOT}/commands/agents/app/domain-layer-agent.md for detailed implementation rules

Core Rules Summary#

โœ… Required Patterns#

  1. Entity extends Equatable
  2. Repository Interface uses I prefix
  3. UseCase uses Optional Constructor Injection (const GetPostsUsecase([IRepo? repo]))
  4. UseCase creates ConcreteRepo directly as default value (repo ?? const ConcreteRepo())
  5. Write unit tests for all UseCases

โŒ Prohibited Patterns#

// โŒ @injectable ์–ด๋…ธํ…Œ์ด์…˜ ๊ธˆ์ง€
// @injectable
// class GetPostsUseCase { ... }

// โŒ Getting Repository via getIt prohibited
// IRepo get repo => getIt<IRepo>();

// โŒ Relative path import prohibited
// import '../entity/post.dart';

Test Patterns#

setUp(() {
  mockRepository = MockI{Feature}Repository();
  usecase = Get{Entity}sUsecase(mockRepository);  // ์ง์ ‘ ์ฃผ์ž…
});
// getIt.reset() ๋ถˆํ•„์š”!