| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /feature:domain |
| Aliases | /domain, /domain:create |
| Category | kobic-workflow |
| Complexity | moderate |
| MCP Servers | serena, 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๋ก ํ์ํ ๋์๋ง ์ฝค๋ง๋ก ๋์ดํด ๊ณจ๋ผ์ ๋ง๋ค ์ ์์ต๋๋ค.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ๊ธฐ์กด ํจํด ์ดํด๋ณด๊ธฐ โ ์ด๋ฏธ ๋ง๋ค์ด์ง ๋น์ทํ ์ฝ๋๋ฅผ ๋ถ์ํด ๊ฐ์ ์คํ์ผ๋ก ๋ง์ถฅ๋๋ค.
- ๋ฐ์ดํฐ ๋ชจ์(Entity) ๋ง๋ค๊ธฐ โ ๋ค๋ฃฐ ๋์์ ํญ๋ชฉ๋ค(์ ๋ชฉ, ์์ฑ์, ์์ฑ์ผ ๋ฑ)์ ์ ์ํฉ๋๋ค.
- ๋ฐ์ดํฐ ์ฝ์(Repository Interface) ๋ง๋ค๊ธฐ โ ์กฐํยท์์ฑยท์์ ยท์ญ์ ๊ฐ์ ๊ท๊ฒฉ์ ์ ํฉ๋๋ค.
- ๋์(UseCase) ๋ง๋ค๊ธฐ โ ๊ฐ ๊ธฐ๋ฅ ๋์์ ํ๋์ฉ ์ฝ๋๋ก ์์ฑํฉ๋๋ค.
- ํ ์คํธ ๋ง๋ค๊ธฐ โ ๊ฐ ๋์์ด ์ ์๋ํ๋์ง ๊ฒ์ฆํ๋ ์ฝ๋๋ฅผ ํจ๊ป ์์ฑํฉ๋๋ค.
- ๋ง๋ฌด๋ฆฌ ์ ๋ฆฌ โ ์ฝ๋ ์์ฑ ๋ช ๋ น์ ๋๋ ค ์๋ ์์ฑ ํ์ผ๋ค์ ์์ฑํฉ๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Triggers#
- When a new Feature Domain Layer is needed
- When Entity, Repository Interface, and UseCase generation are needed
/feature:createorchestration Step 3
Context Trigger Pattern#
/feature:domain {feature_name} {entity_name} [--options]Parameters#
| Parameter | Required | Description | Example |
|---|---|---|---|
feature_name | โ | Feature module name (snake_case) | community, chat |
entity_name | โ | Entity name (PascalCase) | Post, Message |
--location | โ | Location | application, 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.dart2. 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.dartPost-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 applicationCreate 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#
- Entity extends Equatable
- Repository Interface uses
Iprefix - UseCase uses Optional Constructor Injection (
const GetPostsUsecase([IRepo? repo])) - UseCase creates ConcreteRepo directly as default value (
repo ?? const ConcreteRepo()) - 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() ๋ถํ์!