| ํญ๋ชฉ | ๋ด์ฉ |
| Invoke | /feature:data |
| Aliases | /data, /data:create |
| Category | petmedi-workflow |
| Complexity | standard |
| MCP Servers | serena, context7 |
/feature:data#
Context Framework Note: This behavioral instruction activates when Claude Code users type
/feature:data patterns.
Triggers#
- ์๋ก์ด Feature์ Data Layer๊ฐ ํ์ํ ๋
- Repository ๊ตฌํ์ฒด, Serverpod Mixin, Cache ์ ๋ต ๊ตฌํ์ด ํ์ํ ๋
/feature:create ์ค์ผ์คํธ๋ ์ด์
์ Step 4์์ ํธ์ถ๋ ๋
Context Trigger Pattern#
/feature:data {feature_name} {entity_name} [--options]
Parameters#
| ํ๋ผ๋ฏธํฐ | ํ์ | ์ค๋ช
| ์์ |
feature_name |
โ
|
Feature ๋ชจ๋๋ช
(snake_case) |
community, chat |
entity_name |
โ
|
Entity๋ช
(PascalCase) |
Post, Message |
--location |
โ |
์์น |
application, common, console (๊ธฐ๋ณธ: application) |
--caching |
โ |
์บ์ฑ ์ ๋ต |
swr, cache-first, none (๊ธฐ๋ณธ: swr) |
Behavioral Flow#
1. ๊ธฐ์กด ํจํด ๋ถ์#
Serena MCP๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ์กด Data Layer ํจํด ๋ถ์:
- feature/application/community/lib/src/data/repository/community_repository.dart
- feature/application/community/lib/src/data/repository/mixins/community_serverpod_mixin.dart
2. Repository ๊ตฌํ์ฒด ์์ฑ#
import 'package:dependencies/dependencies.dart';
import 'package:serverpod_service/serverpod_service.dart';
import '../../domain/repository/i_{feature}_repository.dart';
import 'mixins/{feature}_serverpod_mixin.dart';
/// {Feature} Repository ๊ตฌํ์ฒด
@LazySingleton(as: I{Feature}Repository)
class {Feature}Repository
with {Feature}ServerpodMixin
implements I{Feature}Repository {
/// [{Feature}Repository]๋ฅผ ์์ฑํฉ๋๋ค.
{Feature}Repository(
this._serverpodService,
this._database,
);
final ServerpodService _serverpodService;
final {Feature}Database _database;
@override
ServerpodClient get client => _serverpodService.client;
@override
{Entity}Dao get {entity}Dao => _database.{entity}Dao;
}
3. Serverpod Mixin ์์ฑ (๋ค์์คํ์ด์ค ํ์!)#
import 'package:dependencies/dependencies.dart';
import 'package:serverpod_service/serverpod_service.dart' as pod; // โ
๋ค์์คํ์ด์ค ํ์
import '../../domain/entity/{entity}.dart';
import '../../domain/repository/i_{feature}_repository.dart';
/// {Feature} Serverpod API Mixin
mixin {Feature}ServerpodMixin implements I{Feature}Repository {
/// Serverpod ํด๋ผ์ด์ธํธ
pod.ServerpodClient get client; // โ
๋ค์์คํ์ด์ค ์ฌ์ฉ
/// {Entity} DAO
{Entity}Dao get {entity}Dao;
@override
Future<Either<Failure, {Entity}>> create{Entity}({
required {Entity}Category category,
required String title,
required String content,
}) async {
try {
// 1. Domain โ Protocol ๋ณํ (๋ค์์คํ์ด์ค ์ฌ์ฉ)
final request = pod.{Entity}CreateRequest(
category: _categoryToProtocol(category),
title: title,
content: content,
);
// 2. API ํธ์ถ
final response = await client.{feature}.create{Entity}(request);
// 3. Protocol โ Entity ๋ณํ
final entity = _map{Entity}FromProtocol(response);
// 4. ์บ์์ ์ ์ฅ
await {entity}Dao.save{Entity}(entity);
return Right(entity);
} on Exception catch (error, stackTrace) {
return left(
RepositoryFailure(
'Failed to create {entity}',
error: error,
stackTrace: stackTrace,
),
);
}
}
// DTO ๋ณํ ํฌํผ
{Entity} _map{Entity}FromProtocol(pod.{Entity} response) {
return {Entity}(
id: response.id ?? 0,
title: response.title,
category: _categoryFromProtocol(response.category),
// ...
);
}
// Protocol โ Domain ๋ณํ
{Entity}Category _categoryFromProtocol(pod.{Entity}Category category) {
switch (category) {
case pod.{Entity}Category.qna:
return {Entity}Category.qna;
// ...
}
}
// Domain โ Protocol ๋ณํ
pod.{Entity}Category _categoryToProtocol({Entity}Category category) {
switch (category) {
case {Entity}Category.qna:
return pod.{Entity}Category.qna;
// ...
}
}
}
4. SWR ์บ์ฑ ํจํด#
@override
Future<Either<Failure, {Entity}ListResult>> get{Entity}s({...}) async {
try {
// 1. ์บ์ ํ์ธ
final cachedData = await {entity}Dao.getAll{Entity}s(limit: limit, offset: offset);
if (cachedData.isNotEmpty) {
// 2. ์บ์ ๋ฐ์ดํฐ ์ฆ์ ๋ฐํ
final entities = cachedData.map((data) => {entity}Dao.dataTo{Entity}(data)).toList();
final cachedResult = {Entity}ListResult(items: entities, ...);
// 3. ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ฐฑ์ (SWR)
unawaited(_refresh{Entity}sInBackground(...));
return Right(cachedResult);
}
// 4. ์บ์ ์์ผ๋ฉด API ํธ์ถ
return _fetchAndCache{Entity}s(...);
} on Exception catch (error, stackTrace) {
return left(RepositoryFailure(...));
}
}
5. Cache-First Stream ํจํด#
@override
Stream<CacheFirstResult<{Entity}ListResult>> get{Entity}sAsStream({...}) async* {
// 1. ์บ์ ์กฐํ
final cachedData = await {entity}Dao.getAll{Entity}s(...);
// 2. ์บ์๊ฐ ์์ผ๋ฉด ์ฆ์ emit
if (cachedData.isNotEmpty) {
yield CacheFirstResult(data: cachedResult, fromCache: true, isRefreshing: true);
}
// 3. ๋คํธ์ํฌ์์ ์ต์ ๋ฐ์ดํฐ
final result = await get{Entity}s(...);
// 4. ๋คํธ์ํฌ ๊ฒฐ๊ณผ emit
yield* result.fold(
(failure) async* { if (cachedData.isEmpty) throw Exception(...); },
(networkResult) async* {
yield CacheFirstResult(data: networkResult, fromCache: false, isRefreshing: false);
},
);
}
Output Files#
feature/{location}/{feature_name}/lib/src/data/
โโโ repository/
โ โโโ {feature}_repository.dart
โ โโโ mixins/
โ โ โโโ {feature}_serverpod_mixin.dart
โ โโโ repository.dart # export
โโโ cache/
โ โโโ {entity}_cache_repository.dart
โโโ local/
โโโ tables/
โ โโโ {entity}_table.dart
โโโ dao/
โ โโโ {entity}_dao.dart
โโโ {feature}_database.dart
Post-Generation Commands#
# ์ฝ๋ ์์ฑ (Drift, Injectable)
melos exec --scope={feature_name} -- "dart run build_runner build --delete-conflicting-outputs"
MCP Integration#
- Serena: ๊ธฐ์กด Data Layer ํจํด ๋ถ์, ์ฌ๋ณผ ๊ฒ์
- Context7: Serverpod, Drift ๋ฌธ์ ์ฐธ์กฐ
Examples#
๊ฒ์๊ธ Data Layer ์์ฑ#
/feature:data community Post --location application --caching swr
์ฑํ
๋ฉ์์ง Data Layer ์์ฑ#
/feature:data chat Message --location application --caching cache-first
์ฐธ์กฐ ์์ด์ ํธ#
์์ธ ๊ตฌํ ๊ท์น์ ~/.claude/commands/agents/data-layer-agent.md ์ฐธ์กฐ
ํต์ฌ ๊ท์น ์์ฝ#
โ
Critical Import ํจํด#
// Repository ๊ตฌํ์ฒด
import 'package:dependencies/dependencies.dart';
// Mixin (๋ค์์คํ์ด์ค ํ์!)
import 'package:serverpod_service/serverpod_service.dart' as pod;
mixin {Feature}ServerpodMixin implements I{Feature}Repository {
pod.ServerpodClient get client; // โ
๋ค์์คํ์ด์ค ์ฌ์ฉ
// Domain Entity ์ฌ์ฉ (๋ค์์คํ์ด์ค ์์)
{Entity}Category category = {Entity}Category.qna;
// Serverpod DTO ์ฌ์ฉ (๋ค์์คํ์ด์ค ์ฌ์ฉ)
pod.{Entity}Category apiCategory = pod.{Entity}Category.qna;
}
์บ์ฑ ์ ๋ต ์ ํ#
| ์ ๋ต | ์ฌ์ฉ ์์ | ํน์ง |
| SWR | ์ค์๊ฐ ๋ฐ์ดํฐ | ์บ์ ์ฆ์ ๋ฐํ + ๋ฐฑ๊ทธ๋ผ์ด๋ ๊ฐฑ์ |
| Cache-First | ์ ์ ๋ฐ์ดํฐ | ์บ์ ์์ผ๋ฉด ๋คํธ์ํฌ ํธ์ถ ์ ํจ |
| None | ํญ์ ์ต์ ํ์ | ๋งค๋ฒ ๋คํธ์ํฌ ํธ์ถ |