watermelon_mediator 0.0.5 watermelon_mediator: ^0.0.5 copied to clipboard
A pure dart package to use the Mediator design pattern in all dart projects, specially flutter mobile apps.
Intro #
This is a 'Make it easy' package that makes your code style easy and more readable and make your code highly clean.
It combines the Mediator design pattern, Single Responsibility principle, and CQRS pattern.
#
For complete explanation and learning of design patterns that used in this package Open this link
Read First #
Let me explain with an example. You have a multi-layer project. Layer A needs to use some services from layer B. Each service has an implementation logic. So, you are forced to create an instance or use a Dependency Injection to call the instance logic method of Layer B in Layer A.
Example: Layer A
car.getDate();
car.getName(carId);
car.getNameListBy(filter);
.
.
.
Cons: You should import multiple instances for each logic.
With Watermelon Mediator...
First, you separate logic into a single responsibility, one class-one task(In Layer B).
Then, you register it in Meditor to let Meditor know about this task.
In the end, you call the Watermelon Mediator instance
_mediator.push(..)
in place you need it (In Layer A ), and the
Mediator instance handles the rest.
( You ask Maybe) How does the 'push method' recognize the related #
logic implementation? #
If you go above and check the without Meditor example code again, you see most of the time, you have data to pass. Now, let us use this data model class as a key:
final createCarCommand = createCarCommand(name, age);
_mediator.push(createCarCommand)
The Watermelon Mediator has a register method to let the Meditor find the relation between 'createCarCommand' and its implementation.
In summary, you followed those design patterns like this:
1- Meditor for having a central instance manager between layers or the whole app.
2- Command and Query by setting the purpose for your application task.
The request in Meditor the same as writing, by naming like this,
createCarCommand(),
and it is same as reading by naming like this
readCarInfoQuery()
. A pattern to follow.
3- Single Responsibility for forcing each Request to Meditor(Command or Query / Write or Read) has only one business task to handle.
All three above make your code easy to read, maintain, and expand.
Implementation #
Install #
In the command line:
flutter pub add watermelon_mediator
or in pubspec.yaml:
dependencies: watermelon_mediator: ^0.0.1
Main parts #
The package has four main parts:
1- Meditor Instance
2- Request
3- Request Handler
4- Relate Request to Request Handle
How to make it easy?(Meditor Instance) #
final query = ExampleQuery();
final response = await _mediator.pushAsync<ExampleQuery, ExampleResponse>(query);
// Or ExampleCommand, this naming back to you
// about how you want to use Command and
// Query pattern depends on your business logic
How config it?(Request & Request Handler) #
class AddExampleCommand implements RequestModel<void> {
AddExampleCommand({required this.data});
final Data data;
}
The RequestModel
model is part of a package that lets the package know
that the example class is a 'Request' type(The key for recognizing).
Request types are like event parameters that can carry data.
Below part is your task handler and purpose business logic part, which you should write the logic that is related to the purpose; for example, add a car
class AddExampleCommandHandler implements RequestHandler<AddExampleCommand, void> {
final mockCars = MockUpData();
@override
void handle(AddExampleCommand request) {
// You don't need to write here if you don't
// need a synchronous method.
throw UnimplementedError();
}
@override
Future<void> handleAsync(AddExampleCommand request) {
// You don't need to write here if you don't
// need an asynchronous method.
throw UnimplementedError();
}
}
RequestHandler
accepts a generic type, one for RequestModel
and
another one for ResponseModel.
You are free to create a custom ResponseModel to
have more clean code.
Your handler class offers two 'handle' methods, one
for synchronous and one for asynchronous. Logically, if
you use one, you should leave the other.
Register(Relate Request to Request Handle) #
@MediatSRequest(requestName: 'AddCarRequest')
class AddExampleCommand...
@MediatSHandler(requestName: 'AddCarRequest')
class AddExampleCommandHandler...
The above annotation @MediatSRequest
lets WaterMelon Meditor know
we have a request type with this key: "requestName".
We use the same requestName for Handler, too, so Meditor Register request and request handler with exact "requestName".
Remember, "requestName" should be unique in the whole app for each request to Meditor.
At the end, you should add
RequestsRegister();
at the top of the runApp(...);
method in your main.dart
class, and
then run this command
dart run watermelon_mediator -path [WRITE THE SAVE LOCATION OF RequestsRegister() PATH]
Write your path like this: lib/domain/watermelon_mediator
You can remove the -path argument; it generates the RequestsRegister
class in the lib directory.
How to use(Example)? #
For example, in your state manager or any part of your
architect that you want to call a service or logic,
you can import watermelon_mediator
package. By passing
the request model to it, it finds the handler automatically
and pass the result to it to you.
class CarSateManager {
final _mediator = Mediator();
Future<List<Car>> getCarList() async {
final query = CarListQuery(pageIndex: 0, orderNum: 2);
final response =
await _mediator.pushAsync<CarListQuery, CarListResponse>(query);
return response.cars;
}
void addCar(String name) {
final command = AddCarCommand(car: car);
_mediator.push(car);
}
}
Make it easy