LogoSkills

feature-data

기능의 Clean Architecture 데이터 레이어를 만듭니다 — Repository, 서버 통신용 Serverpod Mixin, 캐시/DAO/테이블 파일을 생성하고 이어서 돌릴 build_runner 명령까지 안내합니다.

/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)을 정합니다.

안에서 무슨 일이 벌어지나요#

  1. 기존 코드 살펴보기 — 이미 비슷하게 만들어 둔 데이터 코드가 있는지 먼저 분석해 같은 모양새를 따릅니다.
  2. Repository 만들기 — 데이터 요청을 총괄하는 대표 코드를 생성합니다.
  3. 서버 연결 코드 만들기 — 서버에 실제로 요청을 보내고, 서버 형식 ↔ 앱 형식으로 데이터를 변환하는 코드를 만듭니다.
  4. 캐시 전략 적용 — 목록 데이터는 SWR(빠르게 보여주고 뒤에서 갱신), 단건 데이터는 캐시 우선 방식으로 임시 저장 규칙을 붙입니다.
  5. 마무리 — 생성된 파일 목록을 정리하고, 코드 자동 완성(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:create orchestration Step 4

Context Trigger Pattern#

/cc-flutter:feature:data {feature_name} {entity_name} [--options]

Parameters#

ParameterRequiredDescriptionExample
feature_nameFeature module name (snake_case)community, chat
entity_nameEntity name (PascalCase)Post, Message
--locationLocationapplication, common, console (default: application)
--cachingCaching strategyswr, 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.dart

2. 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.dart

Post-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 swr

Create Chat Message Data Layer#

/cc-flutter:feature:data chat Message --location application --caching cache-first

Reference 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)#

StrategyEntry PointReturn TypeSuitable For
SWRwatchStream()Stream<Either<Failure, T>>GET lists, externally mutable
CacheFirstexecute()Future<Either<Failure, T>>GET single, offline first
NetworkFirstexecute()Future<Either<Failure, T>>Payment/auth, always need latest