Módulo 01: Fundamentos y Buenas Prácticas

Modularity

Arquitectura Software

# Modularity (Modularización) ## ¿Qué es la Modularización? La modularización es el proceso de dividir una aplicación en módulos independientes, cada uno con una responsabilidad específica. En Flutter, esto significa organizar el código en paquetes reutilizables y desacoplados. ## Beneficios de la Modularización ### 1. **Reutilización de Código** ### 2. **Desarrollo Paralelo** ### 3. **Compilación más Rápida** ### 4. **Fácil Mantenimiento** ### 5. **Testing Simplificado** ## Visualización de Dependencias ```mermaid graph TD subgraph "App" Main[Main App] end subgraph "Features" Auth[Authentication Feature] Products[Products Feature] Cart[Cart Feature] end subgraph "Core" Network[Network Module] Theme[Theme Module] Utils[Utils Module] end Main --> Auth Main --> Products Main --> Cart Auth --> Network Auth --> Utils Products --> Network Products --> Theme Cart --> Products style Main fill:#f9f,stroke:#333,stroke-width:2px style Auth fill:#bbf,stroke:#333,stroke-width:2px style Products fill:#bbf,stroke:#333,stroke-width:2px style Cart fill:#bbf,stroke:#333,stroke-width:2px style Network fill:#dfd,stroke:#333,stroke-width:2px style Theme fill:#dfd,stroke:#333,stroke-width:2px style Utils fill:#dfd,stroke:#333,stroke-width:2px ``` ## Tipos de Modularización en Flutter ### Modularización por Features ``` lib/ ├── features/ │ ├── authentication/ │ │ ├── data/ │ │ ├── domain/ │ │ └── presentation/ │ ├── products/ │ │ ├── data/ │ │ ├── domain/ │ │ └── presentation/ │ └── cart/ │ ├── data/ │ ├── domain/ │ └── presentation/ └── core/ ├── network/ ├── theme/ └── utils/ ``` ### Modularización por Packages ``` my_app/ ├── packages/ │ ├── core/ │ │ └── lib/ │ │ ├── error/ │ │ ├── network/ │ │ └── utils/ │ ├── authentication/ │ │ ├── lib/ │ │ └── pubspec.yaml │ └── products/ │ ├── lib/ │ └── pubspec.yaml └── pubspec.yaml ``` ## Implementación Práctica ### Paso 1: Crear un Módulo Independiente ```yaml # packages/authentication/pubspec.yaml name: authentication description: Authentication module version: 1.0.0 environment: sdk: ">=3.0.0 <4.0.0" dependencies: flutter: sdk: flutter # Dependencias específicas del módulo flutter_bloc: ^8.1.0 dartz: ^0.10.1 equatable: ^2.0.0 ``` ### Paso 2: Definir la Interface Pública del Módulo ```dart // packages/authentication/lib/authentication.dart library authentication; // Exportar solo lo necesario export 'src/domain/entities/user.dart' ; export 'src/domain/usecases/login.dart' ; export 'src/domain/usecases/logout.dart' ; export 'src/presentation/bloc/auth_bloc.dart' ; export 'src/presentation/screens/login_screen.dart' ; // NO exportar detalles de implementación // ❌ No hacer: export 'src/data/datasources/...' ; ``` ### Paso 3: Implementar el Módulo ```dart // packages/authentication/lib/src/domain/entities/user.dart class User { final String id; final String name; final String email; User({required this.id, required this.name, required this.email}); } // packages/authentication/lib/src/domain/repositories/auth_repository.dart abstract class AuthRepository { Future> login(String email, String password); Future> logout(); Future> getCurrentUser(); } // packages/authentication/lib/src/domain/usecases/login.dart class Login { final AuthRepository repository; Login(this.repository); Future> call(String email, String password) { return repository.login(email, password); } } ``` ### Paso 4: Usar el Módulo en la App Principal ```yaml # pubspec.yaml de la app principal dependencies: flutter: sdk: flutter authentication: path: packages/authentication products: path: packages/products ``` ```dart // main.dart import 'package:authentication/authentication.dart'; import 'package:products/products.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider(create: (_) => sl()), BlocProvider(create: (_) => sl()), ], child: MaterialApp( home: AuthWrapper(), ), ); } } ``` ## Patrón Feature-First Structure ```dart // lib/features/products/products.dart library products; export 'domain/entities/product.dart'; export 'presentation/bloc/products_bloc.dart'; export 'presentation/pages/products_page.dart'; export 'di/products_injection.dart'; // Inyección de dependencias del módulo class ProductsModule { static void initialize() { // Register dependencies sl.registerLazySingleton( () => ProductRepositoryImpl( remoteDataSource: sl(), localDataSource: sl(), ), ); sl.registerLazySingleton(() => GetProducts(sl())); sl.registerFactory(() => ProductsBloc(getProducts: sl())); } } ``` ## Comunicación Entre Módulos ### ❌ Mal: Acoplamiento Directo ```dart // products_bloc.dart class ProductsBloc extends Bloc { // ❌ Dependencia directa de otro módulo final AuthBloc authBloc; ProductsBloc(this.authBloc) { // Acoplamiento fuerte if (authBloc.state is Authenticated) { add(LoadProducts()); } } } ``` ### ✅ Bien: Event Bus o Streams ```dart // core/events/app_events.dart abstract class AppEvent {} class UserLoggedIn extends AppEvent { final String userId; UserLoggedIn(this.userId); } class UserLoggedOut extends AppEvent {} // core/events/event_bus.dart class EventBus { final _controller = StreamController.broadcast(); Stream get events => _controller.stream; void fire(AppEvent event) { _controller.add(event); } } // authentication module class AuthBloc extends Bloc { final EventBus eventBus; Future _onLoginSuccess(User user) async { emit(Authenticated(user)); eventBus.fire(UserLoggedIn(user.id)); // ✅ Comunicación desacoplada } } // products module class ProductsBloc extends Bloc { final EventBus eventBus; StreamSubscription? _eventSubscription; ProductsBloc(this.eventBus) { _eventSubscription = eventBus.events.listen((event) { if (event is UserLoggedIn) { add(LoadProducts()); } else if (event is UserLoggedOut) { add(ClearProducts()); } }); } @override Future close() { _eventSubscription?.cancel(); return super.close(); } } ``` ## Módulos Core Comunes ### 1. Core/Network Module ```dart // core/network/network_module.dart class NetworkModule { static Dio createDio() { final dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: Duration(seconds: 30), receiveTimeout: Duration(seconds: 30), )); dio.interceptors.addAll([ LogInterceptor(), AuthInterceptor(), RetryInterceptor(), ]); return dio; } } ``` ### 2. Core/Theme Module ```dart // core/theme/app_theme.dart class AppTheme { static ThemeData get lightTheme => ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.light, ), ); static ThemeData get darkTheme => ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), ); } ``` ### 3. Core/Utils Module ```dart // core/utils/validators.dart class Validators { static String? validateEmail(String? value) { if (value == null || value.isEmpty) { return 'Email is required'; } final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(value)) { return 'Invalid email format'; } return null; } } ``` ## Gestión de Dependencias Entre Módulos ```yaml # packages/products/pubspec.yaml dependencies: # ✅ Solo dependencias necesarias core: path: ../core # ❌ Evitar dependencias circulares # authentication: # path: ../authentication ``` ## Testing de Módulos ```dart // packages/authentication/test/domain/usecases/login_test.dart void main() { late Login usecase; late MockAuthRepository mockRepository; setUp(() { mockRepository = MockAuthRepository(); usecase = Login(mockRepository); }); test('should return User when credentials are correct', () async { // Arrange final tUser = User(id: '1', name: 'Test', email: '[email protected]'); when(mockRepository.login(any, any)) .thenAnswer((_) async => Right(tUser)); // Act final result = await usecase('[email protected]', 'password'); // Assert expect(result, Right(tUser)); }); } ``` ## Mejores Prácticas ### ✅ Hacer 1. **Mantener módulos pequeños y enfocados** 2. **Definir interfaces claras** 3. **Documentar APIs públicas** 4. **Versionar módulos independientemente** 5. **Minimizar dependencias entre módulos** ### ❌ Evitar 1. **Dependencias circulares** 2. **Exposer detalles de implementación** 3. **Módulos demasiado grandes** 4. **Acoplamiento fuerte entre módulos** ## Herramientas Útiles ### Melos - Gestión de Monorepos ```yaml # melos.yaml name: my_app packages: - packages/** scripts: analyze: run: flutter analyze exec: concurrency: 1 test: run: flutter test exec: concurrency: 1 ``` ```bash # Instalar melos dart pub global activate melos # Bootstrap (instalar dependencias de todos los paquetes) melos bootstrap # Ejecutar tests en todos los paquetes melos test # Analizar todos los paquetes melos analyze ``` ## Conclusión La modularización efectiva en Flutter: - ✅ **Mejora la escalabilidad** - ✅ **Facilita el testing** - ✅ **Permite desarrollo paralelo** - ✅ **Reduce tiempos de compilación** - ✅ **Aumenta la reutilización de código** > "Divide y vencerás: Los módulos pequeños y bien definidos son más fáciles de entender, testear y mantener."

Este apartado profundiza en los conceptos clave, proporcionando ejemplos prácticos y mejores prácticas para su aplicación en proyectos reales.

Al comprender estos detalles, podrás diseñar soluciones más robustas, mantenibles y escalables en tus aplicaciones Flutter y Dart.