Skip to main content

Arsitektur Aplikasi

MStore Mobile dibangun dengan Clean Architecture dan BLoC Pattern untuk memastikan separation of concerns, testability, dan maintainability.

πŸ—οΈ Arsitektur Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Presentation Layer                    β”‚
β”‚  (UI, Widgets, Pages, BLoC/Cubit State Management)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Domain Layer                         β”‚
β”‚     (Business Logic, Use Cases, Entities, Repos)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Data Layer                          β”‚
β”‚  (Repository Impl, API, Local DB, Data Sources)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Infrastructure                         β”‚
β”‚     (Dio, Retrofit, Isar, Firebase, MQTT)               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Struktur Folder

lib/
β”œβ”€β”€ app.dart                    # Root application widget
β”œβ”€β”€ main_development.dart       # Development entry point
β”œβ”€β”€ main_staging.dart          # Staging entry point
β”œβ”€β”€ main_production.dart       # Production entry point
β”‚
β”œβ”€β”€ core/                      # Core business logic (Domain + Data)
β”‚   β”œβ”€β”€ auth/                 # Authentication domain
β”‚   β”‚   β”œβ”€β”€ auth_api.dart            # Retrofit API
β”‚   β”‚   β”œβ”€β”€ auth_repository.dart     # Repository interface
β”‚   β”‚   β”œβ”€β”€ auth_service.dart        # Business logic
β”‚   β”‚   └── models/                  # Domain models
β”‚   β”‚
β”‚   β”œβ”€β”€ product/              # Product domain
β”‚   β”œβ”€β”€ inventory/            # Inventory domain
β”‚   β”œβ”€β”€ transaction/          # Transaction domain
β”‚   β”œβ”€β”€ dashboard/            # Dashboard domain
β”‚   β”œβ”€β”€ payments/             # Payments domain
β”‚   β”‚
β”‚   β”œβ”€β”€ network/              # Networking infrastructure
β”‚   β”‚   β”œβ”€β”€ dio_client.dart          # Dio factory
β”‚   β”‚   β”œβ”€β”€ interceptors/            # HTTP interceptors
β”‚   β”‚   └── network_exceptions.dart  # Error handling
β”‚   β”‚
β”‚   β”œβ”€β”€ mqtt/                 # MQTT real-time sync
β”‚   β”œβ”€β”€ firebase/             # Firebase services
β”‚   β”œβ”€β”€ observers/            # BLoC observers
β”‚   └── ...
β”‚
β”œβ”€β”€ features/                 # Feature modules (Presentation)
β”‚   β”œβ”€β”€ cashier/             # Kasir feature
β”‚   β”‚   β”œβ”€β”€ bloc/                   # BLoC state management
β”‚   β”‚   β”œβ”€β”€ pages/                  # UI pages
β”‚   β”‚   β”œβ”€β”€ widgets/                # Feature widgets
β”‚   β”‚   └── models/                 # UI models
β”‚   β”‚
β”‚   β”œβ”€β”€ inventory/           # Inventory feature
β”‚   β”œβ”€β”€ dashboard/           # Dashboard feature
β”‚   β”œβ”€β”€ auth/                # Auth UI
β”‚   β”œβ”€β”€ home/                # Home feature
β”‚   └── ...
β”‚
β”œβ”€β”€ di/                      # Dependency Injection
β”‚   β”œβ”€β”€ injection.dart              # GetIt setup
β”‚   β”œβ”€β”€ injection.config.dart       # Generated DI config
β”‚   └── network_module.dart         # Network DI module
β”‚
β”œβ”€β”€ pkg/                     # Shared utilities & components
β”‚   β”œβ”€β”€ component/                  # Reusable widgets
β”‚   β”œβ”€β”€ theme/                      # Theme configuration
β”‚   β”œβ”€β”€ utils/                      # Helper functions
β”‚   └── common/                     # Constants
β”‚
β”œβ”€β”€ database/                # Local database schemas
β”‚   └── isar/                       # Isar collections
β”‚
β”œβ”€β”€ i18n/                    # Internationalization
β”‚   β”œβ”€β”€ strings.g.dart              # Generated translations
β”‚   β”œβ”€β”€ strings_id.g.dart           # Indonesian
β”‚   └── strings_en.g.dart           # English
β”‚
└── middlewares/             # Router middlewares
    └── auth_middleware.dart        # Auth guard

🎯 Layer Responsibilities

1. Presentation Layer (features/)

Tanggung jawab:
  • Menampilkan UI
  • Menangani user interaction
  • Mengelola UI state dengan BLoC/Cubit
  • Tidak boleh mengakses data source langsung
Komponen:
  • Pages: Full screen widgets
  • Widgets: Reusable UI components
  • BLoC/Cubit: State management
  • Models: UI-specific models (jika berbeda dari domain)
Contoh:
// features/cashier/bloc/cashier_bloc.dart
class CashierBloc extends Bloc<CashierEvent, CashierState> {
  final ProductRepository _productRepository;
  
  CashierBloc(this._productRepository) : super(CashierInitial()) {
    on<LoadProducts>(_onLoadProducts);
  }
  
  Future<void> _onLoadProducts(
    LoadProducts event,
    Emitter<CashierState> emit,
  ) async {
    emit(CashierLoading());
    final result = await _productRepository.getProducts();
    result.fold(
      (failure) => emit(CashierError(failure.message)),
      (products) => emit(CashierLoaded(products)),
    );
  }
}

2. Domain Layer (core/*/)

Tanggung jawab:
  • Business logic
  • Domain models (entities)
  • Repository interfaces
  • Use cases (optional, untuk logic kompleks)
Prinsip:
  • Pure Dart (no Flutter dependencies)
  • Framework-agnostic
  • Testable
Contoh:
// core/product/product_repository.dart
abstract class ProductRepository {
  Future<Either<Failure, List<Product>>> getProducts();
  Future<Either<Failure, Product>> getProductById(String id);
  Future<Either<Failure, void>> createProduct(Product product);
}

3. Data Layer (core/*/)

Tanggung jawab:
  • Implementasi repository
  • API calls (Retrofit)
  • Local database operations (Isar)
  • Data mapping (DTO ↔ Entity)
  • Caching strategy
Komponen:
  • Repository Implementation: Concrete repository
  • API: Retrofit interfaces
  • Local Data Source: Isar collections
  • DTOs: Data Transfer Objects
Contoh:
// core/product/product_repository_retrofit.dart
class ProductRepositoryRetrofit implements ProductRepository {
  final Dio dio;
  final String baseUrl;
  
  @override
  Future<Either<Failure, List<Product>>> getProducts() async {
    try {
      final api = ProductApi(dio, baseUrl: baseUrl);
      final response = await api.getProducts();
      return Right(response.data);
    } on DioException catch (e) {
      return Left(NetworkException.fromDioError(e));
    }
  }
}

4. Infrastructure Layer

Tanggung jawab:
  • HTTP client (Dio)
  • Database (Isar)
  • Firebase services
  • MQTT client
  • External services

πŸ”„ Data Flow

Request Flow (User Action β†’ API)

User Interaction
      ↓
   Widget
      ↓
BLoC Event Dispatch
      ↓
BLoC Event Handler
      ↓
Repository Method Call
      ↓
API Call (Retrofit)
      ↓
Dio Interceptors Pipeline:
  - CorrelationInterceptor (X-Correlation-ID)
  - HeadersInterceptor (Common headers)
  - AuthInterceptor (Authorization token)
  - RefreshTokenInterceptor (Auto refresh)
  - RetryInterceptor (Retry on failure)
  - LoggingInterceptor (Logging)
      ↓
HTTP Request β†’ Backend
      ↓
HTTP Response
      ↓
Repository returns Either<Failure, Data>
      ↓
BLoC emits new State
      ↓
Widget rebuilds with new State

Offline-First Flow

User Action
      ↓
BLoC Event
      ↓
Repository checks local cache (Isar)
      ↓
If cached: Return immediately
      ↓
Background: Sync with API
      ↓
Update local cache
      ↓
Emit updated state

🧩 Design Patterns

1. Repository Pattern

Abstraksi akses data, memisahkan business logic dari data source.
abstract class ProductRepository {
  Future<Either<Failure, List<Product>>> getProducts();
}

class ProductRepositoryRetrofit implements ProductRepository {
  // Implementation with Retrofit
}

class ProductRepositoryMock implements ProductRepository {
  // Mock implementation for testing
}

2. BLoC Pattern

State management dengan event-driven architecture.
// Event
abstract class ProductEvent {}
class LoadProducts extends ProductEvent {}

// State
abstract class ProductState {}
class ProductLoading extends ProductState {}
class ProductLoaded extends ProductState {
  final List<Product> products;
  ProductLoaded(this.products);
}

// BLoC
class ProductBloc extends Bloc<ProductEvent, ProductState> {
  // Handle events and emit states
}

3. Dependency Injection

Menggunakan GetIt + Injectable untuk DI.
@module
abstract class NetworkModule {
  @LazySingleton()
  Dio dio(@Named('baseUrl') String baseUrl) => createDio(baseUrl: baseUrl);
  
  @LazySingleton(as: ProductRepository)
  ProductRepositoryRetrofit productRepository(Dio dio) {
    return ProductRepositoryRetrofit(dio: dio);
  }
}

4. Either Pattern (Functional Error Handling)

Menggunakan Dartz untuk error handling yang eksplisit.
Future<Either<Failure, Product>> getProduct(String id) async {
  try {
    final product = await api.getProduct(id);
    return Right(product);
  } catch (e) {
    return Left(ServerFailure('Failed to fetch product'));
  }
}

// Usage
final result = await repository.getProduct('123');
result.fold(
  (failure) => print('Error: ${failure.message}'),
  (product) => print('Success: ${product.name}'),
);

πŸ” Security Architecture

1. Authentication Flow

Login Request
      ↓
AuthRepository.login()
      ↓
API returns: accessToken, refreshToken
      ↓
Store in secure storage (Isar)
      ↓
AuthInterceptor adds token to all requests
      ↓
On 401 response:
  RefreshTokenInterceptor triggers
      ↓
  Silent refresh with refreshToken
      ↓
  Update tokens
      ↓
  Retry original request

2. Token Management

  • Access Token: Short-lived (15 minutes)
  • Refresh Token: Long-lived (30 days)
  • Auto-refresh: Handled by RefreshTokenInterceptor
  • Storage: Encrypted in Isar database

πŸ“‘ Real-Time Architecture

MQTT Integration

App Start
      ↓
MqttService.InitMQTT()
      ↓
Connect to MQTT broker
      ↓
Subscribe to topics:
  - branch/{branchId}/inventory
  - branch/{branchId}/transactions
  - user/{userId}/notifications
      ↓
On message received:
  Parse payload
      ↓
  Update local database (Isar)
      ↓
  Emit BLoC event
      ↓
  UI updates automatically

πŸ—„οΈ Database Architecture

Isar (Local NoSQL Database)

@collection
class ProductLocal {
  Id id = Isar.autoIncrement;
  
  @Index()
  late String productId;
  
  late String name;
  late double price;
  late int stock;
  
  @Index()
  late DateTime updatedAt;
}
Strategi:
  • Write-through cache
  • Background sync
  • Conflict resolution (last-write-wins)

🎨 UI Architecture

Platform-Adaptive Design

if (Platform.isIOS) {
  return CupertinoApp.router(
    theme: iosTheme,
    routerConfig: GlobalRouter,
  );
} else {
  return MaterialApp.router(
    theme: androidTheme,
    routerConfig: GlobalRouter,
  );
}

Theme System

  • AppTheme: Centralized theme configuration
  • Theme Tailor: Type-safe theme extensions
  • Dark Mode: Full support

πŸ“Š Observability

Logging

// AppLog - Structured logging
AppLog.i('category', 'message', meta: {'key': 'value'});
AppLog.e('category', 'error', exception, stackTrace);

// BLoC Observer
class AppBlocObserver extends BlocObserver {
  @override
  void onEvent(Bloc bloc, Object? event) {
    AppLog.i('bloc.${bloc.runtimeType}', 'event: $event');
  }
}

Crash Reporting

  • Firebase Crashlytics: Production crashes
  • AppLog: Development logging
  • Sentry (optional): Error tracking

πŸ§ͺ Testing Architecture

test/
β”œβ”€β”€ unit/              # Unit tests (business logic)
β”œβ”€β”€ widget/            # Widget tests (UI)
β”œβ”€β”€ integration/       # Integration tests
└── mocks/             # Mock implementations

Next Steps


Arsitektur ini dirancang untuk:
  • βœ… Scalability
  • βœ… Maintainability
  • βœ… Testability
  • βœ… Separation of Concerns
  • βœ… Code Reusability