Repository navigation
Expand file tree
/
Copy pathQuick.Core.Mediator.Abstractions.pas
More file actions
254 lines (211 loc) · 10.2 KB
/
Copy pathQuick.Core.Mediator.Abstractions.pas
File metadata and controls
254 lines (211 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
{ ***************************************************************************
Copyright (c) 2016-2026 Kike Perez
Unit : Quick.Core.Mediator.Abstractions
Description : Mediator pattern abstractions — interfaces and base classes
Author : Kike Perez
Version : 1.0
Created : 29/04/2026
Modified : 08/05/2026
This file is part of QuickCore: https://github.com/exilon/QuickCore
***************************************************************************
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************** }
unit Quick.Core.Mediator.Abstractions;
{$i QuickCore.inc}
interface
uses
System.Rtti,
System.SysUtils,
System.TypInfo;
type
EMediatorError = class(Exception);
// ── Non-generic dispatch interfaces (used internally by TMediator) ─────────
/// <summary>Non-generic base for all request handlers. TMediator calls HandleObj
/// at dispatch time — implement via TRequestHandler<TReq,TResp> instead.</summary>
IRequestHandlerBase = interface
['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
function HandleObj(const aRequest: TObject): TValue;
end;
/// <summary>Non-generic base for all notification handlers.</summary>
INotificationHandlerBase = interface
['{B2C3D4E5-F6A7-8901-BCDE-F01234567890}']
procedure HandleObj(const aNotification: TObject);
end;
/// <summary>Delegate type for the next step in a pipeline chain.</summary>
TPipelineDelegate = reference to function(const aRequest: TObject): TValue;
/// <summary>Non-generic base for pipeline behaviors (middleware).
/// Implement via TPipelineBehavior<TReq,TResp> instead.</summary>
IPipelineBehaviorBase = interface
['{C3D4E5F6-A7B8-9012-CDEF-012345678901}']
function HandleObj(const aRequest: TObject; aNext: TPipelineDelegate): TValue;
end;
// ── Optional marker interfaces ──────────────────────────────────────────────
/// <summary>Optional marker for request objects that return TResponse.
/// Implement to get compile-time documentation of the response type.</summary>
IRequest<TResponse> = interface
['{D4E5F6A7-B8C9-0123-DEFA-123456789012}']
end;
/// <summary>Optional marker for command requests (no response value).</summary>
IRequest = interface
['{E5A6B7C8-D9E0-1234-5678-90ABCDEF1234}']
end;
/// <summary>Marker interface for notification objects published to multiple handlers.</summary>
INotification = interface
['{E5F6A7B8-C9D0-1234-EFAB-234567890123}']
end;
// ── Typed generic handler interfaces ───────────────────────────────────────
/// <summary>Handles a request of TRequest type, returning TResponse.
/// Equivalent to IRequestHandler<TRequest, TResponse> in MediatR.
///
/// Usage: implement via TRequestHandler<TRequest, TResponse> base class.</summary>
IRequestHandler<TRequest: class; TResponse> = interface(IRequestHandlerBase)
['{F6A7B8C9-D0E1-2345-FABC-345678901234}']
function Handle(const aRequest: TRequest): TResponse;
end;
/// <summary>Handles a notification of TNotification type (fan-out: many handlers per notification).
/// Equivalent to INotificationHandler<TNotification> in MediatR.
///
/// Usage: implement via TNotificationHandler<TNotification> base class.</summary>
INotificationHandler<TNotification: class> = interface(INotificationHandlerBase)
['{A7B8C9D0-E1F2-3456-ABCD-456789012345}']
procedure Handle(const aNotification: TNotification);
end;
/// <summary>Delegate type for pipeline behaviors (typed version).</summary>
TRequestHandlerDelegate<TResponse> = reference to function: TResponse;
/// <summary>Pipeline behavior (middleware) that wraps request handling.
/// Equivalent to IPipelineBehavior<TRequest, TResponse> in MediatR.
///
/// Usage: implement via TPipelineBehavior<TRequest, TResponse> base class.
///
/// Example:
/// TLoggingBehavior = class(TPipelineBehavior<TMyRequest, TMyResponse>)
/// function Handle(const req: TMyRequest;
/// aNext: TRequestHandlerDelegate<TMyResponse>): TMyResponse; override;
/// end;</summary>
IPipelineBehavior<TRequest: class; TResponse> = interface(IPipelineBehaviorBase)
['{B8C9D0E1-F2A3-4567-BCDE-567890123456}']
function Handle(const aRequest: TRequest;
aNext: TRequestHandlerDelegate<TResponse>): TResponse;
end;
// ── Core mediator interface ─────────────────────────────────────────────────
/// <summary>
/// Central mediator — dispatches requests to handlers and publishes notifications.
/// Equivalent to IMediator in MediatR.
///
/// Inject IMediator or resolve TMediator for the generic Send<TResponse> helper.
///
/// Usage:
/// // Send a query (with response)
/// result := mediator.Send(TGetProductQuery.Create(id)).AsType<TProductDto>;
///
/// // Or with typed helper (requires TMediator, not IMediator):
/// result := (mediator as TMediator).SendRequest<TProductDto>(TGetProductQuery.Create(id));
///
/// // Publish a notification (fan-out)
/// mediator.Publish(TProductCreatedEvent.Create(id, name));
/// </summary>
IMediator = interface
['{C9D0E1F2-A3B4-5678-CDEF-678901234567}']
/// <summary>Send a request to its single handler. Returns TValue wrapping the response.
/// Raises EMediatorError if no handler is registered for the request type.</summary>
function Send(const aRequest: TObject): TValue;
/// <summary>Publish a notification to all registered handlers (zero or more).</summary>
procedure Publish(const aNotification: TObject);
end;
// ── Abstract base classes (users extend these) ─────────────────────────────
/// <summary>
/// Abstract base class for request handlers.
/// Extend this and override Handle to implement your handler.
///
/// Usage:
/// TGetProductHandler = class(TRequestHandler<TGetProductQuery, TProductDto>)
/// function Handle(const req: TGetProductQuery): TProductDto; override;
/// end;
///
/// // Register:
/// mediatorBuilder.AddRequestHandler<TGetProductQuery, TProductDto, TGetProductHandler>
/// </summary>
TRequestHandler<TRequest: class; TResponse> = class abstract(TInterfacedObject,
IRequestHandlerBase, IRequestHandler<TRequest, TResponse>)
public
function Handle(const aRequest: TRequest): TResponse; virtual; abstract;
/// <summary>Called by TMediator at dispatch time. Do not override.</summary>
function HandleObj(const aRequest: TObject): TValue;
end;
/// <summary>
/// Abstract base class for notification handlers.
///
/// Usage:
/// TEmailOnProductCreated = class(TNotificationHandler<TProductCreatedEvent>)
/// procedure Handle(const notif: TProductCreatedEvent); override;
/// end;
/// </summary>
TNotificationHandler<TNotification: class> = class abstract(TInterfacedObject,
INotificationHandlerBase, INotificationHandler<TNotification>)
public
procedure Handle(const aNotification: TNotification); virtual; abstract;
/// <summary>Called by TMediator at dispatch time. Do not override.</summary>
procedure HandleObj(const aNotification: TObject);
end;
/// <summary>
/// Abstract base class for pipeline behaviors.
///
/// Usage:
/// TLoggingBehavior = class(TPipelineBehavior<TMyReq, TMyResp>)
/// function Handle(const req: TMyReq;
/// aNext: TRequestHandlerDelegate<TMyResp>): TMyResp; override;
/// end;
///
/// // Register:
/// mediatorBuilder.AddPipelineBehavior<TMyReq, TMyResp, TLoggingBehavior>
/// </summary>
TPipelineBehavior<TRequest: class; TResponse> = class abstract(TInterfacedObject,
IPipelineBehaviorBase, IPipelineBehavior<TRequest, TResponse>)
public
function Handle(const aRequest: TRequest;
aNext: TRequestHandlerDelegate<TResponse>): TResponse; virtual; abstract;
/// <summary>Called by TMediator at dispatch time. Do not override.</summary>
function HandleObj(const aRequest: TObject; aNext: TPipelineDelegate): TValue;
end;
implementation
{ TRequestHandler<TRequest, TResponse> }
function TRequestHandler<TRequest, TResponse>.HandleObj(const aRequest: TObject): TValue;
begin
Result := TValue.From<TResponse>(Handle(TRequest(aRequest)));
end;
{ TNotificationHandler<TNotification> }
procedure TNotificationHandler<TNotification>.HandleObj(const aNotification: TObject);
begin
Handle(TNotification(aNotification));
end;
{ TPipelineBehavior<TRequest, TResponse> }
function TPipelineBehavior<TRequest, TResponse>.HandleObj(
const aRequest: TObject; aNext: TPipelineDelegate): TValue;
var
typedNext: TRequestHandlerDelegate<TResponse>;
requestClass: TClass;
begin
requestClass := nil;
if (TypeInfo(TRequest) <> nil) and (GetTypeData(TypeInfo(TRequest)) <> nil) then
requestClass := GetTypeData(TypeInfo(TRequest))^.ClassType;
// Apply this behavior only to matching request types.
// Without this guard, globally-registered behaviors may cast unrelated
// requests and cause access violations.
if (aRequest = nil) or (requestClass = nil) or not aRequest.InheritsFrom(requestClass) then
Exit(aNext(aRequest));
// Wrap the non-generic aNext delegate into the typed delegate
typedNext := function: TResponse
begin
Result := aNext(aRequest).AsType<TResponse>;
end;
Result := TValue.From<TResponse>(Handle(TRequest(aRequest), typedNext));
end;
end.