| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /feature:domain |
| Aliases | /domain, /domain:create |
| Category | petmedi-workflow |
| Complexity | standard |
| MCP Servers | serena, context7 |
/feature:domain#
Context Framework Note: This behavioral instruction activates when Claude Code users type
/feature:domainpatterns.
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 ์ฐธ์กฐ
ํต์ฌ ๊ท์น ์์ฝ#
โ ํ์ ํจํด#
- Entity๋ Equatable ์์
- Repository Interface๋
Iprefix - UseCase๋ ์์ ์์ฑ์ ์ฌ์ฉ (
const GetPostsUsecase()) - UseCase๋ ServiceLocator (
get repo => getIt()) - ๋ชจ๋ 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 ๋ฆฌ์
ํ์