LogoCocode Skills

feature:domain

Clean Architecture Domain Layer ์ƒ์„ฑ

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

/feature:domain#

Context Framework Note: This behavioral instruction activates when Claude Code users type /feature:domain patterns.

Triggers#

  • ์ƒˆ๋กœ์šด Feature์˜ Domain Layer๊ฐ€ ํ•„์š”ํ•  ๋•Œ
  • Entity, Repository Interface, UseCase ์ƒ์„ฑ์ด ํ•„์š”ํ•  ๋•Œ
  • /feature:create ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์˜ Step 3์—์„œ ํ˜ธ์ถœ๋  ๋•Œ

Context Trigger Pattern#

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

Parameters#

ํŒŒ๋ผ๋ฏธํ„ฐํ•„์ˆ˜์„ค๋ช…์˜ˆ์‹œ
feature_name โœ… Feature ๋ชจ๋“ˆ๋ช… (snake_case) community, chat
entity_name โœ… Entity๋ช… (PascalCase) Post, Message
--location โŒ ์œ„์น˜ application, common, console (๊ธฐ๋ณธ: application)
--usecases โŒ ์ƒ์„ฑํ•  UseCase "getList, get, create, update, delete"

Behavioral Flow#

1. ๊ธฐ์กด ํŒจํ„ด ๋ถ„์„#

Serena MCP๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ์กด Domain Layer ํŒจํ„ด ๋ถ„์„:
- 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 ์ƒ์„ฑ#

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. Repository Interface ์ƒ์„ฑ#

import 'package:dependencies/dependencies.dart';

/// {Feature} Repository Interface
abstract interface class I{Feature}Repository {
  /// {์—”ํ‹ฐํ‹ฐ} ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  Future<Either<Failure, {Entity}ListResult>> get{Entity}s({
    required int limit,
    required int offset,
    {Entity}Category? category,
  });

  /// {์—”ํ‹ฐํ‹ฐ} ๋ชฉ๋ก์„ Stream์œผ๋กœ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค (Cache-First).
  Stream<CacheFirstResult<{Entity}ListResult>> get{Entity}sAsStream({...});

  /// {์—”ํ‹ฐํ‹ฐ} ๋‹จ๊ฑด ์กฐํšŒ
  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. UseCase ์ƒ์„ฑ (์ƒ์ˆ˜ ์ƒ์„ฑ์ž + ServiceLocator)#

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
    implements UseCase<{Entity}ListResult, Get{Entity}sParams, I{Feature}Repository> {

  /// [Get{Entity}sUsecase]๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  const Get{Entity}sUsecase();  // โœ… ์ƒ์ˆ˜ ์ƒ์„ฑ์ž (ํ•„์ˆ˜)

  @override
  I{Feature}Repository get repo => getIt();  // โœ… ServiceLocator (ํ•„์ˆ˜)

  @override
  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. UseCase ํ…Œ์ŠคํŠธ ์ƒ์„ฑ#

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

  setUpAll(registerFallbackValues);

  setUp(() {
    mockRepository = MockI{Feature}Repository();
    registerTestLazySingleton<I{Feature}Repository>(mockRepository);
    usecase = const Get{Entity}sUsecase();
  });

  tearDown(getIt.reset);

  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#

# ์ฝ”๋“œ ์ƒ์„ฑ (Freezed ๋“ฑ)
melos exec --scope={feature_name} -- "dart run build_runner build --delete-conflicting-outputs"

MCP Integration#

  • Serena: ๊ธฐ์กด Domain Layer ํŒจํ„ด ๋ถ„์„, ์‹ฌ๋ณผ ๊ฒ€์ƒ‰
  • Context7: Clean Architecture, Either ํŒจํ„ด ๋ฌธ์„œ ์ฐธ์กฐ

Examples#

๊ฒŒ์‹œ๊ธ€ Domain ์ƒ์„ฑ#

/feature:domain community Post --location application

์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ Domain ์ƒ์„ฑ#

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

์ฐธ์กฐ ์—์ด์ „ํŠธ#

์ƒ์„ธ ๊ตฌํ˜„ ๊ทœ์น™์€ ~/.claude/commands/agents/domain-layer-agent.md ์ฐธ์กฐ

ํ•ต์‹ฌ ๊ทœ์น™ ์š”์•ฝ#

โœ… ํ•„์ˆ˜ ํŒจํ„ด#

  1. Entity๋Š” Equatable ์ƒ์†
  2. Repository Interface๋Š” I prefix
  3. UseCase๋Š” ์ƒ์ˆ˜ ์ƒ์„ฑ์ž ์‚ฌ์šฉ (const GetPostsUsecase())
  4. UseCase๋Š” ServiceLocator (get repo => getIt())
  5. ๋ชจ๋“  UseCase์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

โŒ ๊ธˆ์ง€ ํŒจํ„ด#

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

// โŒ ์ƒ์„ฑ์ž ์ฃผ์ž… ๊ธˆ์ง€
// GetPostsUseCase(this._repository);

// โŒ ์ƒ๋Œ€ ๊ฒฝ๋กœ import ๊ธˆ์ง€
// import '../entity/post.dart';

ํ…Œ์ŠคํŠธ ํŒจํ„ด#

setUp(() {
  mockRepository = MockI{Feature}Repository();
  registerTestLazySingleton<I{Feature}Repository>(mockRepository);
  usecase = const Get{Entity}sUsecase();  // ์ƒ์ˆ˜ ์ƒ์„ฑ์ž
});

tearDown(getIt.reset);  // GetIt ๋ฆฌ์…‹ ํ•„์ˆ˜