239 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| <!--
 | |
|  * Copyright (C) 2023 WYATT GROUP
 | |
|  * Please see the AUTHORS file for details.
 | |
| 
 | |
|  * This program is free software: you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License as published by
 | |
|  * the Free Software Foundation, either version 3 of the License, or
 | |
|  * any later version.
 | |
| 
 | |
|  * This program is distributed in the hope that it will be useful, 
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
|  * GNU General Public License for more details.
 | |
| 
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program. If not, see <https://www.gnu.org/licenses/>.
 | |
| -->
 | |
| 
 | |
| # Wyatt Architecture
 | |
| 
 | |
| <p align="left">
 | |
|   <a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis"><img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" /></a>
 | |
|   <img src="https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square" alt="SDK: Dart & Flutter" />
 | |
| </p>
 | |
| 
 | |
| The Wyatt Architecture for Flutter. Contains useful classes to help you to create a clean architecture following the Wyatt Architecture. (core, data, domain, presentation).
 | |
| 
 | |
| ## Features
 | |
| 
 | |
| * Usecase
 | |
| * Repository
 | |
| * DataSource
 | |
| * Entity
 | |
| 
 | |
| ## Usage
 | |
| 
 | |
| ### Domain
 | |
| 
 | |
| Create your entities by extending `Entity` :
 | |
| 
 | |
| ```dart
 | |
| class Photo extends Entity {
 | |
|   final int id;
 | |
|   final String url;
 | |
| 
 | |
|   const Photo({
 | |
|     required this.id,
 | |
|     required this.url,
 | |
|   });
 | |
| }
 | |
| ```
 | |
| 
 | |
| Then create the data sources by extending `BaseDataSource` :
 | |
| 
 | |
| ```dart
 | |
| abstract class PhotoRemoteDataSource extends BaseDataSource {
 | |
|   Future<Photo> getPhoto(int id);
 | |
|   Future<List<Photo>> getAllPhotos({int? start, int? limit});
 | |
| }
 | |
| 
 | |
| ```
 | |
| 
 | |
| Then you can create your repositories by extenting `BaseRepository` :
 | |
| 
 | |
| ```dart
 | |
| abstract class PhotoRepository extends BaseRepository {
 | |
|   FutureResult<Photo> getPhoto(int id);
 | |
|   FutureResult<List<Photo>> getAllPhotos({int? start, int? limit});
 | |
| }
 | |
| ```
 | |
| 
 | |
| > Here the repository is just a proxy of the data sources with result type (to have beautiful error handling).
 | |
| 
 | |
| And finaly create your different usecases :
 | |
| 
 | |
| Several use cases are supported :
 | |
| 
 | |
| * Classic usecase :
 | |
| 
 | |
| ```dart
 | |
| class RetrieveAllAlbums extends AsyncUseCase<QueryParameters, List<Album>> {
 | |
|   const RetrieveAllAlbums(this._photoRepository);
 | |
|   final PhotoRepository _photoRepository;
 | |
| 
 | |
|   @override
 | |
|   FutureOrResult<List<Album>> execute(QueryParameters params) {
 | |
|     final albums = _photoRepository.getAllAlbums(
 | |
|       start: params.start,
 | |
|       limit: params.limit,
 | |
|     );
 | |
|     return albums;
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| * No parameter usecase :
 | |
| 
 | |
| ```dart
 | |
| class DisplayFavorites extends NoParamsAsyncUseCase<List<Photo>> {
 | |
|   const DisplayFavorites(this._photoRepository);
 | |
|   final PhotoRepository _photoRepository;
 | |
| 
 | |
|   @override
 | |
|   FutureOrResult<List<Photo>> execute() {
 | |
|     final photos = _photoRepository.getAllPhotosFromFavorites();
 | |
|     return photos;
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| You can add alternatve scenarios and check pre/post conditions using our extensions :
 | |
| 
 | |
| ```dart
 | |
| class RetrieveAllAlbums extends AsyncUseCase<QueryParameters, List<Album>> {
 | |
|   const RetrieveAllAlbums(this._photoRepository);
 | |
|   final PhotoRepository _photoRepository;
 | |
| 
 | |
|   @override
 | |
|   FutureOrResult<List<Album>> execute(QueryParameters params) {
 | |
|     final albums = _photoRepository.getAllAlbums(
 | |
|       start: params.start,
 | |
|       limit: params.limit,
 | |
|     );
 | |
|     return albums;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   FutureOr<void> onStart(QueryParameters? params) {
 | |
|     if (params.start < 0) {
 | |
|       throw const ClientException('Invalid start parameter');
 | |
|     }
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| You can implement error scenarios overriding `onException` , or check postconditions by overriding `onComplete` .
 | |
| 
 | |
| * Stream usecase :
 | |
| 
 | |
| ```dart
 | |
| class RetrieveAllAlbums extends AsyncUseCase<QueryParameters, List<Album>> {
 | |
|   const RetrieveAllAlbums(this._photoRepository);
 | |
|   final PhotoRepository _photoRepository;
 | |
| 
 | |
|   @override
 | |
|   FutureOrResult<List<Album>> execute(QueryParameters params) {
 | |
|     final albums = _photoRepository.getAllAlbums(
 | |
|       start: params.start,
 | |
|       limit: params.limit,
 | |
|     );
 | |
|     return albums;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   FutureOrResult<List<Album>> onException(Object e) => Ok([]);
 | |
| }
 | |
| ```
 | |
| 
 | |
| Please note that to use handlers, call `call` method and not `execute` .
 | |
| 
 | |
| > In fact, here we need a new parameter object, so let's create it:
 | |
| 
 | |
| ```dart
 | |
| class QueryParameters {
 | |
|   final int? start;
 | |
|   final int? limit;
 | |
| 
 | |
|   QueryParameters(this.start, this.limit);
 | |
| }
 | |
| ```
 | |
| 
 | |
| ### Data
 | |
| 
 | |
| We start by creating models for photos and list of photos. You can use `freezed` . The `PhotoModel` extends `Photo` with some de/serializer capabilities. And those are used only in data layer.
 | |
| 
 | |
| Then implements your data sources:
 | |
| 
 | |
| ```dart
 | |
| class PhotoApiDataSourceImpl extends PhotoRemoteDataSource {
 | |
|   final MiddlewareClient _client;
 | |
| 
 | |
|   PhotoApiDataSourceImpl(this._client);
 | |
| 
 | |
|   @override
 | |
|   Future<Photo> getPhoto(int id) async {
 | |
|     final response = await _client.get(Uri.parse('/photos/$id'));
 | |
|     final photo =
 | |
|         PhotoModel.fromJson(jsonDecode(response.body) as Map<String, Object?>);
 | |
|     return photo;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Future<List<Photo>> getAllPhotos({int? start, int? limit}) async {
 | |
|     final startQuery = start.isNotNull ? '_start=$start' : '';
 | |
|     final limitQuery = limit.isNotNull ? '_limit=$limit' : '';
 | |
|     final delimiter1 =
 | |
|         (startQuery.isNotEmpty || limitQuery.isNotEmpty) ? '?' : '';
 | |
|     final delimiter2 =
 | |
|         (startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : '';
 | |
|     final url = '/photos$delimiter1$startQuery$delimiter2$limitQuery';
 | |
|     final response = await _client.get(Uri.parse(url));
 | |
|     final photos =
 | |
|         ListPhotoModel.fromJson({'photos': jsonDecode(response.body)});
 | |
|     return photos.photos;
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| > 1: Note that here we use `MiddlewareClient` from our http package.
 | |
| 
 | |
| > 2: You can create multiple implementations (one real and one mock for example).
 | |
| 
 | |
| And implement the repositories:
 | |
| 
 | |
| ```dart
 | |
| class PhotoRepositoryImpl extends PhotoRepository {
 | |
|   final PhotoRemoteDataSource _photoRemoteDataSource;
 | |
| 
 | |
|   PhotoRepositoryImpl(
 | |
|     this._photoRemoteDataSource,
 | |
|   );
 | |
| 
 | |
|   @override
 | |
|   FutureResult<Photo> getPhoto(int id) => Result.tryCatchAsync(
 | |
|         () => _photoRemoteDataSource.getPhoto(id),
 | |
|         (error) => ServerException('Cannot retrieve photo $id.'),
 | |
|       );
 | |
| 
 | |
|   @override
 | |
|   FutureResult<List<Photo>> getAllPhotos({int? start, int? limit}) async =>
 | |
|       Result.tryCatchAsync(
 | |
|         () => _photoRemoteDataSource.getAllPhotos(start: start, limit: limit),
 | |
|         (error) => ServerException('Cannot retrieve all photos.'),
 | |
|       );
 | |
| }
 | |
| ```
 | |
| 
 | |
| That's all.
 |