Skip to content

Commit

Permalink
[Breaking] Improve request typing (#3)
Browse files Browse the repository at this point in the history
* Infer type from given request

* Update changelog

* Update readme

* Add external integration tests

* Bump `0.2.0`

* Avoid minification

* Add additional event handler
  • Loading branch information
Matthiee authored Dec 26, 2023
1 parent 383af62 commit 9286403
Show file tree
Hide file tree
Showing 16 changed files with 260 additions and 96 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## 0.1.1 - 2023-12-22
## 0.2.0

- `RequestManager.send` now only accepts a single generic argument, `TResponse`, which is the type of the response body. The `TRequest` type argument has been removed. The type of the Response will be inferred based on the given `Request<Response>` (#3)

## 0.1.1

- Add `registerFactory` and `registerFunction` methods to `RequestManager`.

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ Future<void> main() async {
mediator.requests.register(MyQueryHandler());
final response = await mediator.requests
.send<Something, MyQuery>(MyQuery());
final Something response = await mediator.requests.send(MyQuery());
print(response);
}
Expand Down
37 changes: 20 additions & 17 deletions example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,28 @@ Future<void> main() async {
.map((event) => event.count)
.distinct()
.subscribeFunction(
(count) => print('[$CountEvent handler] received count: $count'),
(count) => print('[CountEvent handler] received count: $count'),
);

mediator.events.on<CountEvent>().subscribeFunction(
(count) => print('[Other Event Handler] received: $count'),
);

const getUserQuery = GetUserByIdQuery(123);

print('Sending $getUserQuery request');

final resp =
await mediator.requests.send<User, GetUserByIdQuery>(getUserQuery);
final resp = await mediator.requests.send(getUserQuery);

print('Got $GetUserByIdQuery response: $resp');
print('Got $getUserQuery response: $resp');

print('---');

const order66Command = MyCommand('Order 66');

print('Sending command $order66Command');

await mediator.requests.send<void, MyCommand>(order66Command);
await mediator.requests.send(order66Command);

print('Command $order66Command completed');

Expand All @@ -58,23 +61,23 @@ class CountEvent implements DomainEvent {
const CountEvent(this.count);

@override
String toString() => '$CountEvent(count: $count)';
String toString() => 'CountEvent(count: $count)';
}

class MyCommand implements Command {
final String command;
const MyCommand(this.command);

@override
String toString() => '$MyCommand(command: $command)';
String toString() => 'MyCommand(command: $command)';
}

class MyCommandHandler implements CommandHandler<MyCommand> {
@override
Future<void> handle(MyCommand request) async {
print('[$MyCommandHandler] Executing "$request"');
print('[MyCommandHandler] Executing "$request"');
await Future.delayed(const Duration(milliseconds: 500));
print('[$MyCommandHandler] "$request" completed');
print('[MyCommandHandler] "$request" completed');
}
}

Expand All @@ -83,15 +86,15 @@ class GetUserByIdQuery implements Query<User> {
const GetUserByIdQuery(this.userId);

@override
String toString() => '$GetUserByIdQuery(userId: $userId)';
String toString() => 'GetUserByIdQuery(userId: $userId)';
}

class GetUserByIdQueryHandler implements QueryHandler<User, GetUserByIdQuery> {
@override
Future<User> handle(GetUserByIdQuery request) async {
print('[$GetUserByIdQueryHandler] handeling $request');
print('[GetUserByIdQueryHandler] handeling $request');
final user = await getUserByIdAsync(request.userId);
print('[$GetUserByIdQueryHandler] got $user');
print('[GetUserByIdQueryHandler] got $user');
return user;
}
}
Expand All @@ -100,10 +103,10 @@ class LoggingBehavior implements PipelineBehavior {
@override
Future handle(request, RequestHandlerDelegate next) async {
try {
print('[$LoggingBehavior] [${request.runtimeType}] Before');
print('[LoggingBehavior] [$request] Before');
return await next();
} finally {
print('[$LoggingBehavior] [${request.runtimeType}] After');
print('[LoggingBehavior] [$request] After');
}
}
}
Expand All @@ -115,7 +118,7 @@ class LoggingEventObserver implements EventObserver {
Set<EventHandler<TEvent>> handlers,
) {
print(
'[$LoggingEventObserver] onDispatch "$event" with ${handlers.length} handlers',
'[LoggingEventObserver] onDispatch "$event" with ${handlers.length} handlers',
);
}

Expand All @@ -126,7 +129,7 @@ class LoggingEventObserver implements EventObserver {
Object error,
StackTrace stackTrace,
) {
print('[$LoggingEventObserver] onError $event -> $handler ($error)');
print('[LoggingEventObserver] onError $event -> $handler ($error)');
}

@override
Expand All @@ -143,7 +146,7 @@ class User {
const User(this.id, this.name);

@override
String toString() => '$User(id: $id, name: $name)';
String toString() => 'User(id: $id, name: $name)';
}

Future<User> getUserByIdAsync(int id) async {
Expand Down
19 changes: 11 additions & 8 deletions lib/src/request/handler/request_handler_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,26 @@ class RequestHandlerStore {
_handlers.remove(TRequest);
}

/// Returns the registered [RequestHandler]'s for [TRequest].
RequestHandler<TResponse, TRequest>
getHandlerFor<TResponse, TRequest extends Request<TResponse>>() {
final handler = _handlers[TRequest] ?? _handlerFactories[TRequest]?.call();
/// Returns the registered [RequestHandler]'s for [request].
RequestHandler getHandlerFor<TResponse extends Object?>(
Request<TResponse> request,
) {
final requestType = request.runtimeType;
final handler =
_handlers[requestType] ?? _handlerFactories[requestType]?.call();

assert(
handler != null,
'getHandlerFor<$TResponse, $TRequest> did not have a registered handler. '
'getHandlerFor<$TResponse, $requestType> did not have a registered handler. '
'Make sure to register the request handler first.',
);

assert(
handler is RequestHandler<TResponse, TRequest>,
handler is RequestHandler<TResponse, Request<TResponse>>,
'The registered handler is of the wrong type got $handler but was '
'expecting a type of RequestHandler<$TResponse, $TRequest>',
'expecting a type of RequestHandler<$TResponse, $requestType>',
);

return handler as RequestHandler<TResponse, TRequest>;
return handler!;
}
}
49 changes: 34 additions & 15 deletions lib/src/request/pipeline/pipeline_behavior_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,34 @@ import 'package:dart_mediator/src/request/pipeline/pipeline_configurator.dart';
import 'package:dart_mediator/src/request/pipeline/pipeline_behavior.dart';

class PipelineBehaviorStore implements PipelineConfigurator {
final _handlers = <PipelineBehavior<Object?, Object>>{};
final _handlerFactories = <PipelineBehaviorFactory<Object?, Object>>{};
final _handlers = <Type, List<PipelineBehavior>>{};
final _handlerFactories = <Type, List<PipelineBehaviorFactory>>{};
final _genericHandlers = <PipelineBehavior>{};
final _genericHandlerFactories = <PipelineBehaviorFactory>{};

@override
void register<TResponse extends Object?, TRequest extends Object>(
void register<TResponse extends Object?, TRequest extends Request<TResponse>>(
PipelineBehavior<TResponse, TRequest> behavior,
) {
_handlers.add(behavior);
final handlers = _handlers.putIfAbsent(
TRequest,
() => <PipelineBehavior>[],
);

handlers.add(behavior);
}

@override
void registerFactory<TResponse extends Object?, TRequest extends Object>(
void registerFactory<TResponse extends Object?,
TRequest extends Request<TResponse>>(
PipelineBehaviorFactory<TResponse, TRequest> factory,
) {
_handlerFactories.add(factory);
final handlers = _handlerFactories.putIfAbsent(
TRequest,
() => <PipelineBehaviorFactory>[],
);

handlers.add(factory);
}

@override
Expand All @@ -38,29 +49,37 @@ class PipelineBehaviorStore implements PipelineConfigurator {

@override
void unregister(PipelineBehavior behavior) {
_handlers.remove(behavior);
for (final handlers in _handlers.values) {
handlers.remove(behavior);
}
_genericHandlers.remove(behavior);
}

@override
void unregisterFactory(PipelineBehaviorFactory factory) {
_handlerFactories.remove(factory);
for (final handlers in _handlerFactories.values) {
handlers.remove(factory);
}
_genericHandlerFactories.remove(factory);
}

/// Returns all [PipelineBehavior]'s that match.
List<PipelineBehavior> getPipelines<TResponse extends Object?,
TRequest extends Request<TResponse>>() {
final handlerFactories = _handlerFactories
.whereType<PipelineBehaviorFactory<TResponse, TRequest>>()
.map((factory) => factory());
List<PipelineBehavior> getPipelines(
Request request,
) {
final requestType = request.runtimeType;

final handlerFactories =
_handlerFactories[requestType]?.map((factory) => factory());

final genericFactories =
_genericHandlerFactories.map((factory) => factory());

final handlers = _handlers[requestType];

return [
..._handlers.whereType<PipelineBehavior<TResponse, TRequest>>(),
...handlerFactories,
if (handlers != null) ...handlers,
if (handlerFactories != null) ...handlerFactories,
..._genericHandlers,
...genericFactories,
];
Expand Down
6 changes: 4 additions & 2 deletions lib/src/request/pipeline/pipeline_configurator.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:dart_mediator/src/request/pipeline/pipeline_behavior.dart';
import 'package:dart_mediator/src/request/request.dart';

/// Factory to create a [PipelineBehavior].
typedef PipelineBehaviorFactory<TRequest, TResponse>
Expand All @@ -9,15 +10,16 @@ abstract interface class PipelineConfigurator {
///
/// When using a generic [PipelineBehavior] the [registerGeneric] should be
/// used instead.
void register<TResponse extends Object?, TRequest extends Object>(
void register<TResponse extends Object?, TRequest extends Request<TResponse>>(
PipelineBehavior<TResponse, TRequest> behavior,
);

/// Registers the [factory].
///
/// When using a generic [PipelineBehavior] the [registerGenericFactory] should
/// be used instead.
void registerFactory<TResponse extends Object?, TRequest extends Object>(
void registerFactory<TResponse extends Object?,
TRequest extends Request<TResponse>>(
PipelineBehaviorFactory<TResponse, TRequest> factory,
);

Expand Down
17 changes: 9 additions & 8 deletions lib/src/request/request_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,13 @@ class RequestManager {
/// This request can be wrapped by [PipelineBehavior]'s see [pipeline].
///
/// This will return [TResponse].
Future<TResponse>
send<TResponse extends Object?, TRequest extends Request<TResponse>>(
TRequest request,
Future<TResponse> send<TResponse extends Object?>(
Request<TResponse> request,
) async {
final handler = _requestHandlerStore.getHandlerFor<TResponse, TRequest>();
final handler = _requestHandlerStore.getHandlerFor(request)
as RequestHandler<TResponse, Request<TResponse>>;

final pipelines =
_pipelineBehaviorStore.getPipelines<TResponse, TRequest>();
final pipelines = _pipelineBehaviorStore.getPipelines(request);

FutureOr<TResponse> handle() => handler.handle(request);

Expand All @@ -73,11 +72,13 @@ class RequestManager {
(next, pipeline) => () => pipeline.handle(request, next),
);

final response = await executionPlan();
final futureOrResult = executionPlan();
final response =
futureOrResult is Future ? await futureOrResult : futureOrResult;

assert(
response is TResponse,
'$TRequest expected a return type of $TResponse but '
'$request expected a return type of $TResponse but '
'got one of type ${response.runtimeType}. '
'One of the registered pipelines is not correctly returning the '
'`next()` call. Pipelines used: $pipelines',
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: dart_mediator
description: >
A simple yet highly configurable Mediator implementation
that allows sending requests and publishing events.
version: 0.1.1
version: 0.2.0
repository: https://github.com/MatthiWare/mediator.dart

environment:
Expand Down
3 changes: 1 addition & 2 deletions test/integration/choreography_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ void main() {
),
);

final stock = await mediator.requests
.send<Map<String, int>, GetInventoryQuery>(GetInventoryQuery());
final stock = await mediator.requests.send(GetInventoryQuery());

expect(stock, {
'mouse': 8,
Expand Down
Loading

0 comments on commit 9286403

Please sign in to comment.