LogoSkills

feature-domain

기능의 Clean Architecture 도메인 레이어를 만듭니다 — Entity, Repository 인터페이스, CRUD UseCase 와 각 UseCase 테스트, 실패·예외 파일과 묶음 export 를 함께 생성합니다.

/cc-flutter:feature:domain — 기능의 "핵심 규칙층" 자동 생성#

항목내용
실행 명령/cc-flutter:feature:domain
분류워크플로우
난이도●●○ 보통
MCP 서버serena, context7

한마디로#

앱 기능의 가장 안쪽 뼈대(핵심 규칙·데이터 모양·동작 정의) 를 자동으로 만들어 줍니다. 건물로 치면 인테리어나 외관 전에 먼저 세우는 철골 구조와 설계 도면에 해당하는 부분이에요.

누가·언제 쓰나요#

  • 새로운 기능을 만들기 시작하면서 그 기능의 데이터 구조와 핵심 동작 규칙을 정의해야 할 때
  • 게시글(Post), 메시지(Message)처럼 다룰 대상(Entity) 과 "목록 조회·생성·수정·삭제" 같은 동작(UseCase) 이 필요할 때
  • 전체 기능을 한 번에 만드는 /cc-flutter:feature:create 작업의 3번째 단계로 자동 호출됩니다

무엇을 해주나요#

기능의 도메인(핵심 규칙) 폴더 안에 필요한 파일들을 한 번에 만들어 줍니다.

  • Entity — 다룰 대상의 데이터 모양 정의 (예: post.dart)
  • Repository Interface — 데이터를 가져오고 저장하는 "약속(규격)" 정의 (예: i_community_repository.dart)
  • UseCase — "목록 조회·단건 조회·생성·수정·삭제" 같은 개별 동작 (예: get_posts_usecase.dart)
  • 테스트 코드 — 각 동작이 제대로 작동하는지 검증하는 자동 테스트
  • 그 외 실패 메시지·예외 처리용 파일과 묶음 export 파일까지 함께 정리됩니다

어떻게 쓰나요#

# 게시글(Post) 도메인 만들기
/cc-flutter:feature:domain community Post --location application

# 채팅 메시지(Message) 도메인을, 원하는 동작만 골라서 만들기
/cc-flutter: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
  • /cc-flutter:feature:create orchestration Step 3

Context Trigger Pattern#

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

Parameters#

ParameterRequiredDescriptionExample
feature_nameFeature module name (snake_case)community, chat
entity_nameEntity name (PascalCase)Post, Message
--locationLocationapplication, common, console (default: application)
--usecasesTo 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#

/cc-flutter:feature:domain community Post --location application

Create Chat Message Domain#

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

Reference Agents#

Reference ${CLAUDE_PLUGIN_ROOT}/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() 불필요!