๐จ Skill: Feature-First Architecture
๐ Metadata
| Atributo | Valor |
|---|---|
| ID | flutter-feature-first |
| Nivel | ๐ก Intermedio |
| Versiรณn | 1.0.0 |
| Keywords | feature-first, feature-architecture, feature-driven |
| Referencia | Feature-First Architecture Guide |
๐ Keywords para Invocaciรณn
Usa cualquiera de estos keywords en tus prompts para invocar este skill:
feature-firstfeature-architecturefeature-driven@skill:feature-first
Ejemplos de Prompts
Crea una app con feature-first architecture
Organiza el proyecto con estructura feature-first
@skill:feature-first - Estructura la app por features
๐ Descripciรณn
โ ๏ธ IMPORTANTE: Todos los comandos de este skill deben ejecutarse desde la raรญz del proyecto (donde existe el directorio mobile/). El skill incluye verificaciones para asegurar que se estรก en el directorio correcto antes de ejecutar cualquier comando.
Feature-First Architecture organiza el cรณdigo por features en lugar de por capas tรฉcnicas. Cada feature contiene todo lo necesario (UI, lรณgica, datos) en una carpeta auto-contenida, facilitando la navegaciรณn y el mantenimiento del cรณdigo.
โ Cuรกndo Usar Este Skill
- Proyectos medianos a grandes con mรบltiples features
- Equipos que trabajan en features especรญficas
- Necesitas navegaciรณn rรกpida en el cรณdigo
- Quieres features auto-contenidas y cohesivas
- Prefieres organizaciรณn por dominio de negocio
- Necesitas escalar la app agregando features
- Quieres reducir conflictos de merge en el equipo
โ Cuรกndo NO Usar Este Skill
- Proyectos muy pequeรฑos (1-2 pantallas)
- Aplicaciones con pocas features
- Prefieres organizaciรณn por capas tรฉcnicas (Data/Domain/Presentation)
๐๏ธ Estructura del Proyecto
lib/
โโโ core/
โ โโโ constants/
โ โ โโโ app_constants.dart
โ โ โโโ api_endpoints.dart
โ โโโ theme/
โ โ โโโ app_theme.dart
โ โ โโโ app_colors.dart
โ โ โโโ app_text_styles.dart
โ โโโ widgets/
โ โ โโโ buttons/
โ โ โ โโโ primary_button.dart
โ โ โ โโโ secondary_button.dart
โ โ โโโ inputs/
โ โ โ โโโ text_field.dart
โ โ โ โโโ search_field.dart
โ โ โโโ loading/
โ โ โโโ loading_indicator.dart
โ โโโ router/
โ โ โโโ app_router.dart
โ โ โโโ routes.dart
โ โโโ services/
โ โ โโโ api_service.dart
โ โ โโโ storage_service.dart
โ โ โโโ analytics_service.dart
โ โโโ utils/
โ โ โโโ validators.dart
โ โ โโโ formatters.dart
โ โ โโโ extensions/
โ โ โโโ string_extensions.dart
โ โ โโโ date_extensions.dart
โ โ โโโ context_extensions.dart
โ โโโ error/
โ โโโ failures.dart
โ โโโ exceptions.dart
โ
โโโ features/
โ โโโ authentication/
โ โ โโโ data/
โ โ โ โโโ datasources/
โ โ โ โ โโโ auth_local_datasource.dart
โ โ โ โ โโโ auth_remote_datasource.dart
โ โ โ โโโ models/
โ โ โ โ โโโ user_model.dart
โ โ โ โ โโโ token_model.dart
โ โ โ โโโ repositories/
โ โ โ โโโ auth_repository_impl.dart
โ โ โโโ domain/
โ โ โ โโโ entities/
โ โ โ โ โโโ user.dart
โ โ โ โโโ repositories/
โ โ โ โ โโโ auth_repository.dart
โ โ โ โโโ usecases/
โ โ โ โโโ login_usecase.dart
โ โ โ โโโ register_usecase.dart
โ โ โ โโโ logout_usecase.dart
โ โ โ โโโ get_user_usecase.dart
โ โ โโโ presentation/
โ โ โ โโโ bloc/
โ โ โ โ โโโ auth_bloc.dart
โ โ โ โ โโโ auth_event.dart
โ โ โ โ โโโ auth_state.dart
โ โ โ โ โโโ login/
โ โ โ โ โโโ login_cubit.dart
โ โ โ โ โโโ login_state.dart
โ โ โ โโโ screens/
โ โ โ โ โโโ login_screen.dart
โ โ โ โ โโโ register_screen.dart
โ โ โ โ โโโ forgot_password_screen.dart
โ โ โ โโโ widgets/
โ โ โ โโโ login_form.dart
โ โ โ โโโ register_form.dart
โ โ โ โโโ social_login_buttons.dart
โ โ โโโ authentication.dart // Barrel file
โ โ
โ โโโ products/
โ โ โโโ data/
โ โ โ โโโ datasources/
โ โ โ โ โโโ products_local_datasource.dart
โ โ โ โ โโโ products_remote_datasource.dart
โ โ โ โโโ models/
โ โ โ โ โโโ product_model.dart
โ โ โ โ โโโ category_model.dart
โ โ โ โโโ repositories/
โ โ โ โโโ products_repository_impl.dart
โ โ โโโ domain/
โ โ โ โโโ entities/
โ โ โ โ โโโ product.dart
โ โ โ โ โโโ category.dart
โ โ โ โโโ repositories/
โ โ โ โ โโโ products_repository.dart
โ โ โ โโโ usecases/
โ โ โ โโโ get_products_usecase.dart
โ โ โ โโโ get_product_detail_usecase.dart
โ โ โ โโโ search_products_usecase.dart
โ โ โ โโโ filter_products_usecase.dart
โ โ โโโ presentation/
โ โ โ โโโ bloc/
โ โ โ โ โโโ products_bloc.dart
โ โ โ โ โโโ products_event.dart
โ โ โ โ โโโ products_state.dart
โ โ โ โ โโโ product_detail/
โ โ โ โ โโโ product_detail_cubit.dart
โ โ โ โ โโโ product_detail_state.dart
โ โ โ โโโ screens/
โ โ โ โ โโโ products_screen.dart
โ โ โ โ โโโ product_detail_screen.dart
โ โ โ โ โโโ search_screen.dart
โ โ โ โโโ widgets/
โ โ โ โโโ product_card.dart
โ โ โ โโโ product_grid.dart
โ โ โ โโโ category_filter.dart
โ โ โ โโโ price_filter.dart
โ โ โโโ products.dart // Barrel file
โ โ
โ โโโ cart/
โ โ โโโ data/
โ โ โ โโโ datasources/
โ โ โ โ โโโ cart_local_datasource.dart
โ โ โ โโโ models/
โ โ โ โ โโโ cart_item_model.dart
โ โ โ โโโ repositories/
โ โ โ โโโ cart_repository_impl.dart
โ โ โโโ domain/
โ โ โ โโโ entities/
โ โ โ โ โโโ cart_item.dart
โ โ โ โโโ repositories/
โ โ โ โ โโโ cart_repository.dart
โ โ โ โโโ usecases/
โ โ โ โโโ add_to_cart_usecase.dart
โ โ โ โโโ remove_from_cart_usecase.dart
โ โ โ โโโ update_quantity_usecase.dart
โ โ โ โโโ get_cart_items_usecase.dart
โ โ โโโ presentation/
โ โ โ โโโ bloc/
โ โ โ โ โโโ cart_bloc.dart
โ โ โ โ โโโ cart_event.dart
โ โ โ โ โโโ cart_state.dart
โ โ โ โโโ screens/
โ โ โ โ โโโ cart_screen.dart
โ โ โ โโโ widgets/
โ โ โ โโโ cart_item_card.dart
โ โ โ โโโ cart_summary.dart
โ โ โ โโโ empty_cart.dart
โ โ โโโ cart.dart // Barrel file
โ โ
โ โโโ orders/
โ โ โโโ data/
โ โ โโโ domain/
โ โ โโโ presentation/
โ โ โโโ orders.dart
โ โ
โ โโโ profile/
โ โ โโโ data/
โ โ โโโ domain/
โ โ โโโ presentation/
โ โ โโโ profile.dart
โ โ
โ โโโ settings/
โ โโโ data/
โ โโโ domain/
โ โโโ presentation/
โ โโโ settings.dart
โ
โโโ main.dart
๐ฆ Dependencias Requeridas
dependencies:
flutter:
sdk: flutter
# State Management
flutter_bloc: ^8.1.3
equatable: ^2.0.5
# Navigation
go_router: ^12.1.3
# Dependency Injection
get_it: ^7.6.4
injectable: ^2.3.2
# Networking
dio: ^5.4.0
retrofit: ^4.0.3
# Local Storage
hive: ^2.2.3
hive_flutter: ^1.1.0
# Utils
dartz: ^0.10.1
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
dev_dependencies:
# Code Generation
build_runner: ^2.4.6
freezed: ^2.4.5
json_serializable: ^6.7.1
injectable_generator: ^2.4.1
retrofit_generator: ^8.0.6
hive_generator: ^2.0.1
# Testing
flutter_test:
sdk: flutter
bloc_test: ^9.1.4
mocktail: ^1.0.1
๐ป Implementaciรณn
1. Core - Configuraciรณn de Router
// lib/core/router/app_router.dart
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
import '../../features/authentication/authentication.dart';
import '../../features/products/products.dart';
import '../../features/cart/cart.dart';
import '../../features/orders/orders.dart';
import '../../features/profile/profile.dart';
final appRouter = GoRouter(
initialLocation: '/login',
routes: [
// Authentication Routes
GoRoute(
path: '/login',
name: 'login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/register',
name: 'register',
builder: (context, state) => const RegisterScreen(),
),
// Main App with Bottom Navigation
ShellRoute(
builder: (context, state, child) {
return MainScaffold(child: child);
},
routes: [
// Products Routes
GoRoute(
path: '/products',
name: 'products',
builder: (context, state) => const ProductsScreen(),
routes: [
GoRoute(
path: ':id',
name: 'product-detail',
builder: (context, state) {
final productId = state.pathParameters['id']!;
return ProductDetailScreen(productId: productId);
},
),
],
),
// Cart Routes
GoRoute(
path: '/cart',
name: 'cart',
builder: (context, state) => const CartScreen(),
),
// Orders Routes
GoRoute(
path: '/orders',
name: 'orders',
builder: (context, state) => const OrdersScreen(),
routes: [
GoRoute(
path: ':id',
name: 'order-detail',
builder: (context, state) {
final orderId = state.pathParameters['id']!;
return OrderDetailScreen(orderId: orderId);
},
),
],
),
// Profile Routes
GoRoute(
path: '/profile',
name: 'profile',
builder: (context, state) => const ProfileScreen(),
),
],
),
],
redirect: (context, state) {
// Implementar lรณgica de autenticaciรณn aquรญ
// final isAuthenticated = ...
// if (!isAuthenticated && state.location != '/login') {
// return '/login';
// }
return null;
},
);
2. Dependency Injection
// lib/core/di/injection.dart
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection.config.dart';
final getIt = GetIt.instance;
@InjectableInit()
Future<void> configureDependencies() async {
await getIt.init();
}
// lib/core/di/injection.config.dart (generado)
// Ejecutar desde la raรญz del proyecto:
// cd mobile && dart run build_runner build --delete-conflicting-outputs && cd ..
3. Feature: Authentication
Domain Layer
// lib/features/authentication/domain/entities/user.dart
import 'package:equatable/equatable.dart';
class User extends Equatable {
final String id;
final String email;
final String name;
final String? avatar;
const User({
required this.id,
required this.email,
required this.name,
this.avatar,
});
@override
List<Object?> get props => [id, email, name, avatar];
}
// lib/features/authentication/domain/repositories/auth_repository.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/user.dart';
abstract class AuthRepository {
Future<Either<Failure, User>> login({
required String email,
required String password,
});
Future<Either<Failure, User>> register({
required String email,
required String password,
required String name,
});
Future<Either<Failure, void>> logout();
Future<Either<Failure, User>> getCurrentUser();
}
// lib/features/authentication/domain/usecases/login_usecase.dart
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/error/failures.dart';
import '../entities/user.dart';
import '../repositories/auth_repository.dart';
@injectable
class LoginUseCase {
final AuthRepository repository;
LoginUseCase(this.repository);
Future<Either<Failure, User>> call({
required String email,
required String password,
}) async {
return await repository.login(email: email, password: password);
}
}
Data Layer
// lib/features/authentication/data/models/user_model.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/entities/user.dart';
part 'user_model.freezed.dart';
part 'user_model.g.dart';
@freezed
class UserModel with _$UserModel {
const UserModel._();
const factory UserModel({
required String id,
required String email,
required String name,
String? avatar,
}) = _UserModel;
factory UserModel.fromJson(Map<String, dynamic> json) =>
_$UserModelFromJson(json);
// Convert to domain entity
User toEntity() {
return User(
id: id,
email: email,
name: name,
avatar: avatar,
);
}
// Convert from domain entity
factory UserModel.fromEntity(User user) {
return UserModel(
id: user.id,
email: user.email,
name: user.name,
avatar: user.avatar,
);
}
}
// lib/features/authentication/data/datasources/auth_remote_datasource.dart
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../models/user_model.dart';
abstract class AuthRemoteDataSource {
Future<UserModel> login({required String email, required String password});
Future<UserModel> register({required String email, required String password, required String name});
Future<void> logout();
Future<UserModel> getCurrentUser();
}
@LazySingleton(as: AuthRemoteDataSource)
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final Dio dio;
AuthRemoteDataSourceImpl(this.dio);
@override
Future<UserModel> login({
required String email,
required String password,
}) async {
try {
final response = await dio.post(
'/auth/login',
data: {
'email': email,
'password': password,
},
);
return UserModel.fromJson(response.data['user']);
} catch (e) {
throw Exception('Login failed: $e');
}
}
@override
Future<UserModel> register({
required String email,
required String password,
required String name,
}) async {
try {
final response = await dio.post(
'/auth/register',
data: {
'email': email,
'password': password,
'name': name,
},
);
return UserModel.fromJson(response.data['user']);
} catch (e) {
throw Exception('Registration failed: $e');
}
}
@override
Future<void> logout() async {
try {
await dio.post('/auth/logout');
} catch (e) {
throw Exception('Logout failed: $e');
}
}
@override
Future<UserModel> getCurrentUser() async {
try {
final response = await dio.get('/auth/user');
return UserModel.fromJson(response.data['user']);
} catch (e) {
throw Exception('Get current user failed: $e');
}
}
}
// lib/features/authentication/data/repositories/auth_repository_impl.dart
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/error/failures.dart';
import '../../domain/entities/user.dart';
import '../../domain/repositories/auth_repository.dart';
import '../datasources/auth_remote_datasource.dart';
@LazySingleton(as: AuthRepository)
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
AuthRepositoryImpl(this.remoteDataSource);
@override
Future<Either<Failure, User>> login({
required String email,
required String password,
}) async {
try {
final userModel = await remoteDataSource.login(
email: email,
password: password,
);
return Right(userModel.toEntity());
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
@override
Future<Either<Failure, User>> register({
required String email,
required String password,
required String name,
}) async {
try {
final userModel = await remoteDataSource.register(
email: email,
password: password,
name: name,
);
return Right(userModel.toEntity());
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
@override
Future<Either<Failure, void>> logout() async {
try {
await remoteDataSource.logout();
return const Right(null);
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
@override
Future<Either<Failure, User>> getCurrentUser() async {
try {
final userModel = await remoteDataSource.getCurrentUser();
return Right(userModel.toEntity());
} catch (e) {
return Left(ServerFailure(e.toString()));
}
}
}
Presentation Layer
// lib/features/authentication/presentation/bloc/login/login_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import '../../../domain/usecases/login_usecase.dart';
part 'login_state.dart';
part 'login_cubit.freezed.dart';
@injectable
class LoginCubit extends Cubit<LoginState> {
final LoginUseCase loginUseCase;
LoginCubit(this.loginUseCase) : super(const LoginState.initial());
Future<void> login({
required String email,
required String password,
}) async {
emit(const LoginState.loading());
final result = await loginUseCase(email: email, password: password);
result.fold(
(failure) => emit(LoginState.error(failure.message)),
(user) => emit(LoginState.success(user)),
);
}
}
// lib/features/authentication/presentation/bloc/login/login_state.dart
part of 'login_cubit.dart';
@freezed
class LoginState with _$LoginState {
const factory LoginState.initial() = LoginInitial;
const factory LoginState.loading() = LoginLoading;
const factory LoginState.success(User user) = LoginSuccess;
const factory LoginState.error(String message) = LoginError;
}
// lib/features/authentication/presentation/screens/login_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/di/injection.dart';
import '../../../../core/widgets/buttons/primary_button.dart';
import '../../../../core/theme/app_colors.dart';
import '../bloc/login/login_cubit.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<LoginCubit>(),
child: Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: BlocConsumer<LoginCubit, LoginState>(
listener: (context, state) {
state.maybeWhen(
success: (user) {
context.go('/products');
},
error: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: AppColors.error,
),
);
},
orElse: () {},
);
},
builder: (context, state) {
final isLoading = state is LoginLoading;
return Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: PrimaryButton(
text: 'Login',
isLoading: isLoading,
onPressed: () {
if (_formKey.currentState!.validate()) {
context.read<LoginCubit>().login(
email: _emailController.text,
password: _passwordController.text,
);
}
},
),
),
const SizedBox(height: 16),
TextButton(
onPressed: () => context.push('/register'),
child: const Text('Don\'t have an account? Register'),
),
],
),
),
);
},
),
),
);
}
}
Barrel File
// lib/features/authentication/authentication.dart
// Domain
export 'domain/entities/user.dart';
export 'domain/repositories/auth_repository.dart';
export 'domain/usecases/login_usecase.dart';
export 'domain/usecases/register_usecase.dart';
export 'domain/usecases/logout_usecase.dart';
export 'domain/usecases/get_user_usecase.dart';
// Presentation
export 'presentation/screens/login_screen.dart';
export 'presentation/screens/register_screen.dart';
export 'presentation/bloc/auth_bloc.dart';
export 'presentation/bloc/login/login_cubit.dart';
4. Main Setup
// lib/main.dart
import 'package:flutter/material.dart';
import 'core/di/injection.dart';
import 'core/router/app_router.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure dependency injection
await configureDependencies();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Feature-First App',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
routerConfig: appRouter,
);
}
}
๐ฏ Mejores Prรกcticas
1. Organizaciรณn por Feature
โ DO:
features/
authentication/
data/
domain/
presentation/
authentication.dart
โ DON'T:
data/
authentication/
domain/
authentication/
presentation/
authentication/
2. Barrel Files
โ DO:
// lib/features/products/products.dart
export 'domain/entities/product.dart';
export 'presentation/screens/products_screen.dart';
// Solo exporta APIs pรบblicas
โ DON'T:
// No expongas implementaciones internas
export 'data/datasources/products_remote_datasource.dart'; // โ
export 'data/models/product_model.dart'; // โ
3. Dependencias entre Features
โ DO:
// Usa core para comunicaciรณn entre features
import 'package:app/core/services/event_bus.dart';
// O pasa datos a travรฉs de navigation
context.push('/cart', extra: product);
โ DON'T:
// No importes directamente desde otras features
import '../../products/domain/entities/product.dart'; // โ
4. Testing por Feature
โ DO:
features/
authentication/
test/
unit/
widget/
integration/
๐ Recursos Adicionales
๐ Skills Relacionados
- Clean Architecture - Arquitectura de cada feature
- Modular Architecture - Alternativa modular
- Testing Strategy - Testing de features
Versiรณn: 1.0.0
รltima actualizaciรณn: Diciembre 2025