| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /feature:data |
| Aliases | /data, /data:create |
| Category | kobic-workflow |
| Complexity | moderate |
| MCP Servers | serena, context7 |
/feature:data โ ๋ฐ์ดํฐ ์ฐ๊ฒฐ ๋ด๋น ์ฝ๋ ๋ง๋ค๊ธฐ#
ํ๋ง๋๋ก#
์ฑ ํ๋ฉด๊ณผ ์๋ฒ(๋ฐ์ดํฐ๋ฒ ์ด์ค) ์ฌ์ด๋ฅผ ์ค๊ฐ๋ "ํ๋ฐฐ ๊ธฐ์ฌ" ์ญํ ์ ์ฝ๋๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด ์ฃผ๋ ๋ช ๋ น์ ๋๋ค. ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๊ณ , ์์ฃผ ์ฐ๋ ๊ฑด ํด๋ํฐ ์์ ์ ๊น ์ ์ฅ(์บ์)ํด ๋์ด ํ๋ฉด์ด ๋น ๋ฅด๊ฒ ๋จ๋๋ก ํด ์ค๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ์ ๊ธฐ๋ฅ(์: ์ปค๋ฎค๋ํฐ, ์ฑํ )์ ๋ง๋ค๋ฉด์ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ถ๋ถ์ด ํ์ํ ๋
- ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ ์ฅํ๋ ๊ท์น(Repository, ์๋ฒ ์ฐ๊ฒฐ, ์บ์ ์ ๋ต)์ ํ ๋ฒ์ ๊ฐ์ถ๊ณ ์ถ์ ๋
- ์ ์ฒด ๊ธฐ๋ฅ์ ํต์งธ๋ก ๋ง๋๋
/feature:create๊ณผ์ ์ 4๋ฒ์งธ ๋จ๊ณ๋ก ์๋ ์คํ๋ ๋
๋ฌด์์ ํด์ฃผ๋์#
๊ธฐ๋ฅ ํด๋ ์์ ๋ฐ์ดํฐ ๊ด๋ จ ์ฝ๋ ํ์ผ ๋ฌถ์์ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. ์ฃผ์ ๊ฒฐ๊ณผ๋ฌผ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Repository (
{feature}_repository.dart) โ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ ์ฅํ๋ ์ผ์ ์ด๊ดํ๋ ์ฝ๋ - Serverpod Mixin (
{feature}_serverpod_mixin.dart) โ ์ค์ ์๋ฒ API์ ํต์ ํ๋ ์ฝ๋ -
Cache / Local (
{entity}_cache_repository.dart,{entity}_table.dart,{entity}_dao.dart๋ฑ) โ ๋ฐ์ดํฐ๋ฅผ ํด๋ํฐ ์์ ์ ๊น ์ ์ฅํด ๋๋ ์ฝ๋ - ์์ฑ ํ ์ฝ๋ ์๋ ์์ฑ(build_runner) ๋ช ๋ น๊น์ง ์๋ดํฉ๋๋ค.
์ด๋ป๊ฒ ์ฐ๋์#
# ๊ฒ์๊ธ(Post) ๋ฐ์ดํฐ ์ฝ๋ ๋ง๋ค๊ธฐ โ ๋ชฉ๋ก์ SWR ๋ฐฉ์์ผ๋ก ์บ์
/feature:data community Post --location application --caching swr
# ์ฑํ
๋ฉ์์ง(Message) ๋ฐ์ดํฐ ์ฝ๋ ๋ง๋ค๊ธฐ โ ์บ์ ์ฐ์ ๋ฐฉ์
/feature:data chat Message --location application --caching cache-first
-
์ฒซ ๋ฒ์งธ ๊ฐ(
community)์ ๊ธฐ๋ฅ ์ด๋ฆ, ๋ ๋ฒ์งธ ๊ฐ(Post)์ ๋ค๋ฃฐ ๋ฐ์ดํฐ ์ข ๋ฅ์ ๋๋ค. -
--location์ ์ฝ๋๊ฐ ๋ค์ด๊ฐ ์์น(application/common/console, ๊ธฐ๋ณธ๊ฐapplication). -
--caching์ ์บ์(์์ ์ ์ฅ) ๋ฐฉ์(swr/cache-first/none, ๊ธฐ๋ณธ๊ฐswr)์ ์ ํฉ๋๋ค.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ๊ธฐ์กด ์ฝ๋ ์ดํด๋ณด๊ธฐ โ ์ด๋ฏธ ๋น์ทํ๊ฒ ๋ง๋ค์ด ๋ ๋ฐ์ดํฐ ์ฝ๋๊ฐ ์๋์ง ๋จผ์ ๋ถ์ํด ๊ฐ์ ๋ชจ์์๋ฅผ ๋ฐ๋ฆ ๋๋ค.
- Repository ๋ง๋ค๊ธฐ โ ๋ฐ์ดํฐ ์์ฒญ์ ์ด๊ดํ๋ ๋ํ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
- ์๋ฒ ์ฐ๊ฒฐ ์ฝ๋ ๋ง๋ค๊ธฐ โ ์๋ฒ์ ์ค์ ๋ก ์์ฒญ์ ๋ณด๋ด๊ณ , ์๋ฒ ํ์ โ ์ฑ ํ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ ์ฝ๋๋ฅผ ๋ง๋ญ๋๋ค.
- ์บ์ ์ ๋ต ์ ์ฉ โ ๋ชฉ๋ก ๋ฐ์ดํฐ๋ SWR(๋น ๋ฅด๊ฒ ๋ณด์ฌ์ฃผ๊ณ ๋ค์์ ๊ฐฑ์ ), ๋จ๊ฑด ๋ฐ์ดํฐ๋ ์บ์ ์ฐ์ ๋ฐฉ์์ผ๋ก ์์ ์ ์ฅ ๊ท์น์ ๋ถ์ ๋๋ค.
- ๋ง๋ฌด๋ฆฌ โ ์์ฑ๋ ํ์ผ ๋ชฉ๋ก์ ์ ๋ฆฌํ๊ณ , ์ฝ๋ ์๋ ์์ฑ(build_runner) ๋ช ๋ น์ ์๋ดํฉ๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Triggers#
- When a new Feature Data Layer is needed
- When Repository implementation, Serverpod Mixin, and Cache strategy are needed
/feature:createorchestration Step 4
Context Trigger Pattern#
/feature:data {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) |
--caching | โ | Caching strategy | swr, cache-first, none (default: swr) |
Behavioral Flow#
1. Existing Pattern Analysis#
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.dart2. Generate Repository implementation#
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. Generate Serverpod Mixin (Namespace required!)#
import 'package:dependencies/dependencies.dart';
import 'package:serverpod_service/serverpod_service.dart' as pod; // โ
Namespace required
import '../../domain/entity/{entity}.dart';
import '../../domain/repository/i_{feature}_repository.dart';
/// {Feature} Serverpod API Mixin
mixin {Feature}ServerpodMixin implements I{Feature}Repository {
/// Serverpod client
pod.ServerpodClient get client; // โ
Using namespace
/// {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 Caching Pattern (GET List)#
// Repository Mixin์์ ์ ๋ต ์กฐ๋ฆฝ
@override
Stream<Either<Failure, List<{Entity}>>> get{Entity}s({
String? categoryId,
}) =>
SwrStrategyImpl<List<{Entity}>>(
cacheRepository: {Entity}DataCacheRepository(
{entity}ItemDao: {entity}ItemDao,
),
networkRepository: {Entity}DataNetworkRepository(
openApiService: openApiService,
),
policy: CachePolicies.standard,
).watchStream(
{Entity}DataCacheQuery(categoryId: categoryId),
);
5. CacheFirst Pattern (GET Single)#
@override
Future<Either<Failure, {Entity}?>> get{Entity}Detail({
required String id,
}) =>
CacheFirstStrategyImpl<{Entity}?>(
cacheRepository: {Entity}DetailCacheRepository(
{entity}DetailDao: {entity}DetailDao,
),
networkRepository: {Entity}DetailNetworkRepository(
openApiService: openApiService,
),
policy: CachePolicies.standard,
).execute(
{Entity}DetailCacheQuery(id: id),
);
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.dartPost-Generation Commands#
# Code generation (Drift, Injectable)
melos exec --scope={feature_name} -- " dart run build_runner build --delete-conflicting-outputs "MCP Integration#
- Serena: Existing Data Layer Pattern Analysis, Symbol search
- Context7: Serverpod, Drift Document reference
Examples#
Create Post Data Layer#
/feature:data community Post --location application --caching swrCreate Chat Message Data Layer#
/feature:data chat Message --location application --caching cache-firstReference Agents#
Detailed implementation rules in ${CLAUDE_PLUGIN_ROOT}/commands/agents/app/data-layer-agent.md reference
Core Rules Summary#
โ Critical Import Pattern#
// 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; // โ
Using namespace
// Domain Entity ์ฌ์ฉ (๋ค์์คํ์ด์ค ์์)
{Entity}Category category = {Entity}Category.qna;
// Serverpod DTO ์ฌ์ฉ (๋ค์์คํ์ด์ค ์ฌ์ฉ)
pod.{Entity}Category apiCategory = pod.{Entity}Category.qna;
}
Caching Strategy Selection (details: /client-cache skill reference)#
| Strategy | Entry Point | Return Type | Suitable For |
|---|---|---|---|
| SWR | watchStream() | Stream<Either<Failure, T>> | GET lists, externally mutable |
| CacheFirst | execute() | Future<Either<Failure, T>> | GET single, offline first |
| NetworkFirst | execute() | Future<Either<Failure, T>> | Payment/auth, always need latest |