/cc-flutter:feature:data — 데이터 연결 담당 코드 만들기#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /cc-flutter:feature:data |
| 분류 | 워크플로우 |
| 난이도 | ●●○ 보통 |
| MCP 서버 | serena, context7 |
한마디로#
앱 화면과 서버(데이터베이스) 사이를 오가는 "택배 기사" 역할의 코드를 자동으로 만들어 주는 명령입니다. 서버에서 데이터를 받아오고, 자주 쓰는 건 휴대폰 안에 잠깐 저장(캐시)해 두어 화면이 빠르게 뜨도록 해 줍니다.
누가·언제 쓰나요#
- 새 기능(예: 커뮤니티, 채팅)을 만들면서 서버와 데이터를 주고받는 부분이 필요할 때
- 데이터를 가져오고 저장하는 규칙(Repository, 서버 연결, 캐시 전략)을 한 번에 갖추고 싶을 때
- 전체 기능을 통째로 만드는
/cc-flutter: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 방식으로 캐시
/cc-flutter:feature:data community Post --location application --caching swr
# 채팅 메시지(Message) 데이터 코드 만들기 — 캐시 우선 방식
/cc-flutter: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
/cc-flutter:feature:createorchestration Step 4
Context Trigger Pattern#
/cc-flutter: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#
/cc-flutter:feature:data community Post --location application --caching swrCreate Chat Message Data Layer#
/cc-flutter:feature:data chat Message --location application --caching cache-firstReference Agents#
Detailed implementation rules in ${CLAUDE_PLUGIN_ROOT}/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 |