LogoSkills

feature:data

Clean Architecture Data Layer generation

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/feature:data
Aliases/data, /data:create
Categorypetmedi-workflow
Complexitystandard
MCP Serversserena, context7

/feature:data#

Context Framework Note: This behavioral instruction activates when Claude Code users type /feature:data patterns.

Triggers#

  • When a new Feature Data Layer is needed
  • When Repository implementation, Serverpod Mixin, and Cache strategy are needed
  • /feature:create orchestration Step 4

Context Trigger Pattern#

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

Parameters#

ParameterRequiredDescriptionExample
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.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#

/feature:data community Post --location application --caching swr

Create Chat Message Data Layer#

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

Reference Agents#

Detailed implementation rules in ~/.claude/commands/agents/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
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