2023-04-20 10:40:13 +02:00
using System ;
2017-02-05 23:22:34 +01:00
using System.Collections.Generic ;
2018-12-19 20:50:56 +01:00
using System.Diagnostics ;
2017-02-12 19:04:26 +01:00
using System.Linq ;
2017-01-31 12:01:08 +01:00
using System.Reflection ;
using System.Threading.Tasks ;
2018-12-19 20:50:56 +01:00
using Tapeti.Annotations ;
2017-01-31 12:01:08 +01:00
using Tapeti.Config ;
2019-08-13 20:30:04 +02:00
using Tapeti.Default ;
2017-01-31 12:01:08 +01:00
using Tapeti.Flow.Annotations ;
2023-04-13 08:39:43 +02:00
using Tapeti.Helpers ;
2017-01-31 12:01:08 +01:00
namespace Tapeti.Flow.Default
{
2019-08-14 20:48:40 +02:00
/// <inheritdoc cref="IFlowProvider"/> />
/// <summary>
/// Default implementation for IFlowProvider.
/// </summary>
2017-01-31 12:01:08 +01:00
public class FlowProvider : IFlowProvider , IFlowHandler
{
2019-08-13 20:30:04 +02:00
private readonly ITapetiConfig config ;
2017-02-05 23:22:34 +01:00
private readonly IInternalPublisher publisher ;
2017-01-31 12:01:08 +01:00
2021-05-29 21:51:58 +02:00
/// <summary>
/// </summary>
2019-08-13 20:30:04 +02:00
public FlowProvider ( ITapetiConfig config , IPublisher publisher )
2017-01-31 12:01:08 +01:00
{
this . config = config ;
2017-02-05 23:22:34 +01:00
this . publisher = ( IInternalPublisher ) publisher ;
2017-01-31 12:01:08 +01:00
}
2019-08-14 20:48:40 +02:00
/// <inheritdoc />
2022-11-23 09:13:38 +01:00
public IYieldPoint YieldWithRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , Task < IYieldPoint > > responseHandler ) where TRequest : class where TResponse : class
2017-01-31 12:01:08 +01:00
{
var responseHandlerInfo = GetResponseHandlerInfo ( config , message , responseHandler ) ;
2017-10-17 10:34:07 +02:00
return new DelegateYieldPoint ( context = > SendRequest ( context , message , responseHandlerInfo ) ) ;
2017-01-31 12:01:08 +01:00
}
2022-02-09 12:19:05 +01:00
/// <inheritdoc />
2022-11-23 09:13:38 +01:00
public IYieldPoint YieldWithRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , ValueTask < IYieldPoint > > responseHandler ) where TRequest : class where TResponse : class
2022-02-09 12:19:05 +01:00
{
var responseHandlerInfo = GetResponseHandlerInfo ( config , message , responseHandler ) ;
return new DelegateYieldPoint ( context = > SendRequest ( context , message , responseHandlerInfo ) ) ;
}
2019-08-14 20:48:40 +02:00
/// <inheritdoc />
2022-11-23 09:13:38 +01:00
public IYieldPoint YieldWithRequestSync < TRequest , TResponse > ( TRequest message , Func < TResponse , IYieldPoint > responseHandler ) where TRequest : class where TResponse : class
2017-01-31 12:01:08 +01:00
{
var responseHandlerInfo = GetResponseHandlerInfo ( config , message , responseHandler ) ;
2017-10-17 10:34:07 +02:00
return new DelegateYieldPoint ( context = > SendRequest ( context , message , responseHandlerInfo ) ) ;
2017-01-31 12:01:08 +01:00
}
2019-08-14 20:48:40 +02:00
/// <inheritdoc />
2017-01-31 12:01:08 +01:00
public IFlowParallelRequestBuilder YieldWithParallelRequest ( )
{
2023-04-20 10:40:13 +02:00
return new ParallelRequestBuilder ( config , this , publisher ) ;
2017-01-31 12:01:08 +01:00
}
2019-08-14 20:48:40 +02:00
/// <inheritdoc />
2022-11-23 09:13:38 +01:00
public IYieldPoint EndWithResponse < TResponse > ( TResponse message ) where TResponse : class
2017-01-31 12:01:08 +01:00
{
2017-10-17 10:34:07 +02:00
return new DelegateYieldPoint ( context = > SendResponse ( context , message ) ) ;
2017-01-31 12:01:08 +01:00
}
2019-08-14 20:48:40 +02:00
/// <inheritdoc />
2017-01-31 12:01:08 +01:00
public IYieldPoint End ( )
{
2017-10-17 10:34:07 +02:00
return new DelegateYieldPoint ( EndFlow ) ;
2017-01-31 12:01:08 +01:00
}
2023-04-20 10:40:13 +02:00
internal async Task < MessageProperties > PrepareRequest ( FlowContext context , ResponseHandlerInfo responseHandlerInfo ,
2024-03-13 13:43:43 +01:00
string? convergeMethodName = null , bool convergeMethodTaskSync = false )
2017-01-31 12:01:08 +01:00
{
2022-11-23 12:58:31 +01:00
if ( ! context . HasFlowStateAndLock )
2017-10-17 10:34:07 +02:00
{
2024-04-08 14:20:15 +02:00
await CreateNewFlowState ( context ) . ConfigureAwait ( false ) ;
2018-12-19 21:40:53 +01:00
Debug . Assert ( context . FlowState ! = null , "context.FlowState != null" ) ;
2017-10-17 10:34:07 +02:00
}
2017-01-31 12:01:08 +01:00
var continuationID = Guid . NewGuid ( ) ;
context . FlowState . Continuations . Add ( continuationID ,
2017-02-05 23:22:34 +01:00
new ContinuationMetadata
2017-01-31 12:01:08 +01:00
{
MethodName = responseHandlerInfo . MethodName ,
2017-02-12 19:04:26 +01:00
ConvergeMethodName = convergeMethodName ,
ConvergeMethodSync = convergeMethodTaskSync
2017-02-05 23:22:34 +01:00
} ) ;
2017-01-31 12:01:08 +01:00
2019-08-13 20:30:04 +02:00
var properties = new MessageProperties
2017-01-31 12:01:08 +01:00
{
CorrelationId = continuationID . ToString ( ) ,
ReplyTo = responseHandlerInfo . ReplyToQueue
} ;
2023-04-20 10:40:13 +02:00
return properties ;
}
internal async Task SendRequest ( FlowContext context , object message , ResponseHandlerInfo responseHandlerInfo ,
2024-03-13 13:43:43 +01:00
string? convergeMethodName = null , bool convergeMethodTaskSync = false )
2023-04-20 10:40:13 +02:00
{
2024-04-08 14:20:15 +02:00
var properties = await PrepareRequest ( context , responseHandlerInfo , convergeMethodName , convergeMethodTaskSync ) . ConfigureAwait ( false ) ;
await context . Store ( responseHandlerInfo . IsDurableQueue ) . ConfigureAwait ( false ) ;
2017-10-17 10:34:07 +02:00
2024-04-08 14:20:15 +02:00
await publisher . Publish ( message , properties , true ) . ConfigureAwait ( false ) ;
2017-01-31 12:01:08 +01:00
}
private async Task SendResponse ( FlowContext context , object message )
{
2022-11-23 09:13:38 +01:00
var reply = context . HasFlowStateAndLock
? context . FlowState . Metadata . Reply
: GetReply ( context . HandlerContext ) ;
2017-10-17 10:34:07 +02:00
2017-02-05 23:22:34 +01:00
if ( reply = = null )
throw new YieldPointException ( "No response is required" ) ;
2017-01-31 12:01:08 +01:00
2017-02-05 23:22:34 +01:00
if ( message . GetType ( ) . FullName ! = reply . ResponseTypeName )
throw new YieldPointException ( $"Flow must end with a response message of type {reply.ResponseTypeName}, {message.GetType().FullName} was returned instead" ) ;
2017-01-31 12:01:08 +01:00
2019-08-13 20:30:04 +02:00
var properties = new MessageProperties
{
CorrelationId = reply . CorrelationId
} ;
2017-02-05 23:22:34 +01:00
// TODO disallow if replyto is not specified?
2017-10-17 10:34:07 +02:00
if ( reply . ReplyTo ! = null )
2024-04-08 14:20:15 +02:00
await publisher . PublishDirect ( message , reply . ReplyTo , properties , reply . Mandatory ) . ConfigureAwait ( false ) ;
2017-02-05 23:22:34 +01:00
else
2024-04-08 14:20:15 +02:00
await publisher . Publish ( message , properties , reply . Mandatory ) . ConfigureAwait ( false ) ;
2017-10-17 10:34:07 +02:00
2024-04-08 14:20:15 +02:00
await context . Delete ( ) . ConfigureAwait ( false ) ;
2017-01-31 12:01:08 +01:00
}
2021-12-10 11:45:09 +01:00
internal static async Task EndFlow ( FlowContext context )
2017-01-31 12:01:08 +01:00
{
2024-04-08 14:20:15 +02:00
await context . Delete ( ) . ConfigureAwait ( false ) ;
2017-01-31 12:01:08 +01:00
2023-04-14 15:47:27 +02:00
if ( context is { HasFlowStateAndLock : true , FlowState . Metadata . Reply : { } } )
2017-10-17 10:34:07 +02:00
throw new YieldPointException ( $"Flow must end with a response message of type {context.FlowState.Metadata.Reply.ResponseTypeName}" ) ;
2017-01-31 12:01:08 +01:00
}
2019-08-13 20:30:04 +02:00
private static ResponseHandlerInfo GetResponseHandlerInfo ( ITapetiConfig config , object request , Delegate responseHandler )
2017-01-31 12:01:08 +01:00
{
2019-08-15 17:56:38 +02:00
var requestAttribute = request . GetType ( ) . GetCustomAttribute < RequestAttribute > ( ) ;
if ( requestAttribute ? . Response = = null )
throw new ArgumentException ( $"Request message {request.GetType().Name} must be marked with the Request attribute and a valid Response type" , nameof ( request ) ) ;
2019-08-13 20:30:04 +02:00
var binding = config . Bindings . ForMethod ( responseHandler ) ;
2017-01-31 12:01:08 +01:00
if ( binding = = null )
throw new ArgumentException ( "responseHandler must be a registered message handler" , nameof ( responseHandler ) ) ;
2019-08-15 17:56:38 +02:00
if ( ! binding . Accept ( requestAttribute . Response ) )
2019-04-24 18:04:30 +02:00
throw new ArgumentException ( $"responseHandler must accept message of type {requestAttribute.Response}" , nameof ( responseHandler ) ) ;
2017-01-31 12:01:08 +01:00
2017-02-05 23:22:34 +01:00
var continuationAttribute = binding . Method . GetCustomAttribute < ContinuationAttribute > ( ) ;
if ( continuationAttribute = = null )
2017-02-15 22:05:01 +01:00
throw new ArgumentException ( "responseHandler must be marked with the Continuation attribute" , nameof ( responseHandler ) ) ;
if ( binding . QueueName = = null )
2019-08-15 17:56:38 +02:00
throw new ArgumentException ( "responseHandler is not yet subscribed to a queue, TapetiConnection.Subscribe must be called before starting a flow" , nameof ( responseHandler ) ) ;
2017-02-05 23:22:34 +01:00
2022-11-23 09:13:38 +01:00
return new ResponseHandlerInfo (
MethodSerializer . Serialize ( responseHandler . Method ) ,
binding . QueueName ,
binding . QueueType = = QueueType . Durable
) ;
2017-01-31 12:01:08 +01:00
}
2022-11-23 09:13:38 +01:00
private static ReplyMetadata ? GetReply ( IFlowHandlerContext context )
2017-01-31 12:01:08 +01:00
{
2021-09-02 16:16:11 +02:00
var requestAttribute = context . MessageContext ? . Message ? . GetType ( ) . GetCustomAttribute < RequestAttribute > ( ) ;
2017-02-05 23:22:34 +01:00
if ( requestAttribute ? . Response = = null )
return null ;
2017-01-31 12:01:08 +01:00
2017-02-05 23:22:34 +01:00
return new ReplyMetadata
{
2022-11-23 09:13:38 +01:00
CorrelationId = context . MessageContext ! . Properties . CorrelationId ,
2021-09-02 16:16:11 +02:00
ReplyTo = context . MessageContext . Properties . ReplyTo ,
2019-04-24 18:04:30 +02:00
ResponseTypeName = requestAttribute . Response . FullName ,
2021-09-02 16:16:11 +02:00
Mandatory = context . MessageContext . Properties . Persistent . GetValueOrDefault ( true )
2017-02-05 23:22:34 +01:00
} ;
}
2019-08-13 20:30:04 +02:00
private static async Task CreateNewFlowState ( FlowContext flowContext )
2017-10-17 10:34:07 +02:00
{
2019-08-15 12:04:03 +02:00
var flowStore = flowContext . HandlerContext . Config . DependencyResolver . Resolve < IFlowStore > ( ) ;
2017-10-17 10:34:07 +02:00
var flowID = Guid . NewGuid ( ) ;
2024-04-08 14:20:15 +02:00
var flowStateLock = await flowStore . LockFlowState ( flowID ) . ConfigureAwait ( false ) ;
2017-10-17 10:34:07 +02:00
2022-11-23 09:13:38 +01:00
if ( flowStateLock = = null )
2017-10-17 10:34:07 +02:00
throw new InvalidOperationException ( "Unable to lock a new flow" ) ;
2022-11-23 09:13:38 +01:00
var flowState = new FlowState
2017-10-17 10:34:07 +02:00
{
2022-11-23 09:13:38 +01:00
Metadata = new FlowMetadata ( GetReply ( flowContext . HandlerContext ) )
2017-10-17 10:34:07 +02:00
} ;
2022-11-23 09:13:38 +01:00
flowContext . SetFlowState ( flowState , flowStateLock ) ;
2017-10-17 10:34:07 +02:00
}
2017-02-05 23:22:34 +01:00
2023-04-20 10:40:13 +02:00
2019-08-14 20:48:40 +02:00
/// <inheritdoc />
2022-02-09 11:26:56 +01:00
public async ValueTask Execute ( IFlowHandlerContext context , IYieldPoint yieldPoint )
2017-02-05 23:22:34 +01:00
{
2022-11-22 13:20:47 +01:00
if ( yieldPoint is not DelegateYieldPoint executableYieldPoint )
2022-11-23 09:13:38 +01:00
throw new YieldPointException ( $"Yield point is required in controller {context.Controller?.GetType().Name} for method {context.Method.Name}" ) ;
2017-02-05 23:22:34 +01:00
2022-11-23 09:13:38 +01:00
FlowContext ? flowContext = null ;
2019-08-16 11:47:57 +02:00
var disposeFlowContext = false ;
try
2017-02-05 23:22:34 +01:00
{
2022-11-23 09:13:38 +01:00
if ( context . MessageContext = = null | | ! context . MessageContext . TryGet < FlowMessageContextPayload > ( out var flowPayload ) )
2017-02-05 23:22:34 +01:00
{
2022-11-23 09:13:38 +01:00
flowContext = new FlowContext ( context ) ;
2019-08-16 11:47:57 +02:00
// If we ended up here it is because of a Start. No point in storing the new FlowContext
// in the messageContext as the yield point is the last to execute.
disposeFlowContext = true ;
}
2021-09-02 16:16:11 +02:00
else
flowContext = flowPayload . FlowContext ;
2023-04-20 10:40:13 +02:00
2019-08-16 11:47:57 +02:00
try
{
2024-04-08 14:20:15 +02:00
await executableYieldPoint . Execute ( flowContext ) . ConfigureAwait ( false ) ;
2019-08-16 11:47:57 +02:00
}
catch ( YieldPointException e )
{
// Useful for debugging
2022-11-23 09:13:38 +01:00
e . Data [ "Tapeti.Controller.Name" ] = context . Controller ? . GetType ( ) . FullName ;
2019-08-16 11:47:57 +02:00
e . Data [ "Tapeti.Controller.Method" ] = context . Method . Name ;
throw ;
}
2017-02-05 23:22:34 +01:00
2019-08-16 11:47:57 +02:00
flowContext . EnsureStoreOrDeleteIsCalled ( ) ;
2017-02-05 23:22:34 +01:00
}
2019-08-16 11:47:57 +02:00
finally
2017-02-05 23:22:34 +01:00
{
2019-08-16 11:47:57 +02:00
if ( disposeFlowContext )
2022-11-23 09:13:38 +01:00
flowContext ? . Dispose ( ) ;
2017-02-05 23:22:34 +01:00
}
2017-01-31 12:01:08 +01:00
}
2021-12-09 23:52:25 +01:00
/// <inheritdoc />
2022-11-23 09:13:38 +01:00
public IFlowParallelRequest ? GetParallelRequest ( IFlowHandlerContext context )
2021-12-09 23:52:25 +01:00
{
2022-11-23 09:13:38 +01:00
return context . MessageContext ! = null & & context . MessageContext . TryGet < FlowMessageContextPayload > ( out var flowPayload )
2021-12-10 11:45:09 +01:00
? new ParallelRequest ( config , this , flowPayload . FlowContext )
: null ;
}
2021-12-09 23:52:25 +01:00
2021-12-10 11:45:09 +01:00
/// <inheritdoc />
2022-02-09 11:26:56 +01:00
public ValueTask Converge ( IFlowHandlerContext context )
2021-12-10 11:45:09 +01:00
{
2022-11-23 09:13:38 +01:00
return Execute ( context , new DelegateYieldPoint ( async flowContext = >
{
if ( flowContext . ContinuationMetadata = = null )
throw new InvalidOperationException ( "Missing ContinuationMetadata in FlowContext" ) ;
if ( flowContext . ContinuationMetadata . ConvergeMethodName = = null )
throw new InvalidOperationException ( "Missing ConvergeMethodName in FlowContext ContinuationMetadata" ) ;
2024-04-08 14:20:15 +02:00
await Converge ( flowContext , flowContext . ContinuationMetadata . ConvergeMethodName , flowContext . ContinuationMetadata . ConvergeMethodSync ) . ConfigureAwait ( false ) ;
2022-11-23 09:13:38 +01:00
} ) ) ;
2021-12-09 23:52:25 +01:00
}
2017-10-17 10:34:07 +02:00
2021-12-10 11:45:09 +01:00
internal async Task Converge ( FlowContext flowContext , string convergeMethodName , bool convergeMethodSync )
{
2022-11-23 09:13:38 +01:00
IYieldPoint ? yieldPoint ;
if ( flowContext . HandlerContext = = null )
throw new InvalidOperationException ( $"Missing HandleContext in FlowContext for converge method {convergeMethodName}" ) ;
if ( flowContext . HandlerContext . MessageContext = = null )
throw new InvalidOperationException ( $"Missing MessageContext in FlowContext for converge method {convergeMethodName}" ) ;
2021-12-10 11:45:09 +01:00
if ( ! flowContext . HandlerContext . MessageContext . TryGet < ControllerMessageContextPayload > ( out var controllerPayload ) )
throw new ArgumentException ( "Context does not contain a controller payload" , nameof ( flowContext ) ) ;
2022-11-23 09:13:38 +01:00
if ( controllerPayload . Controller = = null )
throw new InvalidOperationException ( $"Controller is not available for converge method {convergeMethodName} (method is static?)" ) ;
2021-12-10 11:45:09 +01:00
var method = controllerPayload . Controller . GetType ( ) . GetMethod ( convergeMethodName , BindingFlags . NonPublic | BindingFlags . Instance ) ;
if ( method = = null )
throw new ArgumentException ( $"Unknown converge method in controller {controllerPayload.Controller.GetType().Name}: {convergeMethodName}" ) ;
if ( convergeMethodSync )
2022-11-23 09:13:38 +01:00
yieldPoint = ( IYieldPoint ? ) method . Invoke ( controllerPayload . Controller , new object [ ] { } ) ;
2021-12-10 11:45:09 +01:00
else
2022-11-23 09:13:38 +01:00
{
var yieldPointTask = method . Invoke ( controllerPayload . Controller , new object [ ] { } ) ;
if ( yieldPointTask = = null )
throw new YieldPointException ( $"Yield point is required in controller {controllerPayload.Controller.GetType().Name} for converge method {convergeMethodName}" ) ;
2024-04-08 14:20:15 +02:00
yieldPoint = await ( ( Task < IYieldPoint > ) yieldPointTask ) . ConfigureAwait ( false ) ;
2022-11-23 09:13:38 +01:00
}
2021-12-10 11:45:09 +01:00
if ( yieldPoint = = null )
throw new YieldPointException ( $"Yield point is required in controller {controllerPayload.Controller.GetType().Name} for converge method {convergeMethodName}" ) ;
2024-04-08 14:20:15 +02:00
await Execute ( flowContext . HandlerContext , yieldPoint ) . ConfigureAwait ( false ) ;
2021-12-10 11:45:09 +01:00
}
2017-02-12 19:04:26 +01:00
2021-12-10 09:56:37 +01:00
private class ParallelRequestBuilder : IFlowParallelRequestBuilder
{
2017-02-12 19:04:26 +01:00
private class RequestInfo
2017-01-31 12:01:08 +01:00
{
2022-11-23 09:13:38 +01:00
public object Message { get ; }
public ResponseHandlerInfo ResponseHandlerInfo { get ; }
public RequestInfo ( object message , ResponseHandlerInfo responseHandlerInfo )
{
Message = message ;
ResponseHandlerInfo = responseHandlerInfo ;
}
2017-01-31 12:01:08 +01:00
}
2019-08-13 20:30:04 +02:00
private readonly ITapetiConfig config ;
2021-12-10 11:45:09 +01:00
private readonly FlowProvider flowProvider ;
2023-04-20 10:40:13 +02:00
private readonly IInternalPublisher publisher ;
2022-11-17 16:47:07 +01:00
private readonly List < RequestInfo > requests = new ( ) ;
2017-01-31 12:01:08 +01:00
2023-04-20 10:40:13 +02:00
public ParallelRequestBuilder ( ITapetiConfig config , FlowProvider flowProvider , IInternalPublisher publisher )
2017-01-31 12:01:08 +01:00
{
this . config = config ;
2021-12-10 11:45:09 +01:00
this . flowProvider = flowProvider ;
2023-04-20 10:40:13 +02:00
this . publisher = publisher ;
2017-01-31 12:01:08 +01:00
}
2022-11-23 09:13:38 +01:00
public IFlowParallelRequestBuilder AddRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , Task > responseHandler ) where TRequest : class where TResponse : class
2017-01-31 12:01:08 +01:00
{
2021-12-10 09:56:37 +01:00
return InternalAddRequest ( message , responseHandler ) ;
2017-01-31 12:01:08 +01:00
}
2022-11-23 09:13:38 +01:00
public IFlowParallelRequestBuilder AddRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , ValueTask > responseHandler ) where TRequest : class where TResponse : class
2022-02-09 12:19:05 +01:00
{
return InternalAddRequest ( message , responseHandler ) ;
}
2017-01-31 12:01:08 +01:00
2022-11-23 09:13:38 +01:00
public IFlowParallelRequestBuilder AddRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , IFlowParallelRequest , Task > responseHandler ) where TRequest : class where TResponse : class
2021-12-09 23:52:25 +01:00
{
2021-12-10 09:56:37 +01:00
return InternalAddRequest ( message , responseHandler ) ;
2021-12-09 23:52:25 +01:00
}
2022-11-23 09:13:38 +01:00
public IFlowParallelRequestBuilder AddRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , IFlowParallelRequest , ValueTask > responseHandler ) where TRequest : class where TResponse : class
2022-02-09 12:19:05 +01:00
{
return InternalAddRequest ( message , responseHandler ) ;
}
2021-12-09 23:52:25 +01:00
2022-11-23 09:13:38 +01:00
public IFlowParallelRequestBuilder AddRequestSync < TRequest , TResponse > ( TRequest message , Action < TResponse > responseHandler ) where TRequest : class where TResponse : class
2017-01-31 12:01:08 +01:00
{
2021-12-10 09:56:37 +01:00
return InternalAddRequest ( message , responseHandler ) ;
2017-01-31 12:01:08 +01:00
}
2022-11-23 09:13:38 +01:00
public IFlowParallelRequestBuilder AddRequestSync < TRequest , TResponse > ( TRequest message , Action < TResponse , IFlowParallelRequest > responseHandler ) where TRequest : class where TResponse : class
2022-02-09 12:19:05 +01:00
{
return InternalAddRequest ( message , responseHandler ) ;
}
2017-01-31 12:01:08 +01:00
2022-02-09 09:19:56 +01:00
private IFlowParallelRequestBuilder InternalAddRequest ( object message , Delegate responseHandler )
2021-12-09 23:52:25 +01:00
{
2022-11-23 09:13:38 +01:00
requests . Add ( new RequestInfo ( message , GetResponseHandlerInfo ( config , message , responseHandler ) ) ) ;
2021-12-09 23:52:25 +01:00
return this ;
}
2021-12-10 11:45:09 +01:00
public IYieldPoint Yield ( Func < Task < IYieldPoint > > continuation , FlowNoRequestsBehaviour noRequestsBehaviour = FlowNoRequestsBehaviour . Exception )
2017-01-31 12:01:08 +01:00
{
2021-12-10 11:45:09 +01:00
return BuildYieldPoint ( continuation , false , noRequestsBehaviour ) ;
2017-01-31 12:01:08 +01:00
}
2021-12-10 11:45:09 +01:00
public IYieldPoint YieldSync ( Func < IYieldPoint > continuation , FlowNoRequestsBehaviour noRequestsBehaviour = FlowNoRequestsBehaviour . Exception )
2017-01-31 12:01:08 +01:00
{
2021-12-10 11:45:09 +01:00
return BuildYieldPoint ( continuation , true , noRequestsBehaviour ) ;
2017-01-31 12:01:08 +01:00
}
2017-02-12 19:04:26 +01:00
2021-12-10 11:45:09 +01:00
private IYieldPoint BuildYieldPoint ( Delegate convergeMethod , bool convergeMethodSync , FlowNoRequestsBehaviour noRequestsBehaviour = FlowNoRequestsBehaviour . Exception )
2017-02-12 19:04:26 +01:00
{
2019-08-15 17:45:39 +02:00
if ( requests . Count = = 0 )
2021-12-10 11:45:09 +01:00
{
2022-11-22 13:20:47 +01:00
return noRequestsBehaviour switch
2021-12-10 11:45:09 +01:00
{
2022-11-22 13:20:47 +01:00
FlowNoRequestsBehaviour . Exception = > throw new YieldPointException ( "At least one request must be added before yielding a parallel request" ) ,
FlowNoRequestsBehaviour . Converge = > new DelegateYieldPoint ( context = > flowProvider . Converge ( context , convergeMethod . Method . Name , convergeMethodSync ) ) ,
FlowNoRequestsBehaviour . EndFlow = > new DelegateYieldPoint ( EndFlow ) ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( noRequestsBehaviour ) , noRequestsBehaviour , null )
} ;
2021-12-10 11:45:09 +01:00
}
2019-08-15 17:45:39 +02:00
2022-11-23 09:13:38 +01:00
if ( convergeMethod . Method = = null )
2017-02-12 19:04:26 +01:00
throw new ArgumentNullException ( nameof ( convergeMethod ) ) ;
2021-12-10 11:45:09 +01:00
return new DelegateYieldPoint ( async context = >
2017-02-12 19:04:26 +01:00
{
2022-11-23 09:13:38 +01:00
if ( convergeMethod . Method . DeclaringType ! = context . HandlerContext . Controller ? . GetType ( ) )
2017-02-12 19:04:26 +01:00
throw new YieldPointException ( "Converge method must be in the same controller class" ) ;
2023-04-20 10:40:13 +02:00
var preparedRequests = new List < PreparedRequest > ( ) ;
2023-04-06 07:44:45 +02:00
foreach ( var requestInfo in requests )
{
2023-04-20 10:40:13 +02:00
var properties = await flowProvider . PrepareRequest (
2023-04-06 07:44:45 +02:00
context ,
2017-02-12 19:04:26 +01:00
requestInfo . ResponseHandlerInfo ,
convergeMethod . Method . Name ,
2024-04-08 14:20:15 +02:00
convergeMethodSync ) . ConfigureAwait ( false ) ;
2023-04-20 10:40:13 +02:00
preparedRequests . Add ( new PreparedRequest ( requestInfo . Message , properties ) ) ;
2023-04-06 07:44:45 +02:00
}
2021-12-10 11:45:09 +01:00
2024-04-08 14:20:15 +02:00
await context . Store ( requests . Any ( i = > i . ResponseHandlerInfo . IsDurableQueue ) ) . ConfigureAwait ( false ) ;
await Task . WhenAll ( preparedRequests . Select ( r = > publisher . Publish ( r . Message , r . Properties , true ) ) ) . ConfigureAwait ( false ) ;
2017-02-12 19:04:26 +01:00
} ) ;
}
}
2017-01-31 12:01:08 +01:00
2021-12-09 23:52:25 +01:00
private class ParallelRequest : IFlowParallelRequest
{
private readonly ITapetiConfig config ;
2021-12-10 11:45:09 +01:00
private readonly FlowProvider flowProvider ;
2021-12-10 09:56:37 +01:00
private readonly FlowContext flowContext ;
2021-12-09 23:52:25 +01:00
2021-12-10 11:45:09 +01:00
public ParallelRequest ( ITapetiConfig config , FlowProvider flowProvider , FlowContext flowContext )
2021-12-09 23:52:25 +01:00
{
this . config = config ;
2021-12-10 11:45:09 +01:00
this . flowProvider = flowProvider ;
2021-12-10 09:56:37 +01:00
this . flowContext = flowContext ;
2021-12-09 23:52:25 +01:00
}
2021-12-10 09:56:37 +01:00
2022-11-23 09:13:38 +01:00
public Task AddRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , Task > responseHandler ) where TRequest : class where TResponse : class
2021-12-09 23:52:25 +01:00
{
2021-12-10 09:56:37 +01:00
return InternalAddRequest ( message , responseHandler ) ;
2021-12-09 23:52:25 +01:00
}
2021-12-10 09:56:37 +01:00
2022-11-23 09:13:38 +01:00
public Task AddRequest < TRequest , TResponse > ( TRequest message , Func < TResponse , IFlowParallelRequest , Task > responseHandler ) where TRequest : class where TResponse : class
2021-12-09 23:52:25 +01:00
{
2021-12-10 09:56:37 +01:00
return InternalAddRequest ( message , responseHandler ) ;
2021-12-09 23:52:25 +01:00
}
2021-12-10 09:56:37 +01:00
2022-11-23 09:13:38 +01:00
public Task AddRequestSync < TRequest , TResponse > ( TRequest message , Action < TResponse > responseHandler ) where TRequest : class where TResponse : class
2021-12-09 23:52:25 +01:00
{
2021-12-10 09:56:37 +01:00
return InternalAddRequest ( message , responseHandler ) ;
2021-12-09 23:52:25 +01:00
}
2021-12-10 09:56:37 +01:00
private Task InternalAddRequest ( object message , Delegate responseHandler )
2021-12-09 23:52:25 +01:00
{
2021-12-10 09:56:37 +01:00
var responseHandlerInfo = GetResponseHandlerInfo ( config , message , responseHandler ) ;
2021-12-10 11:45:09 +01:00
2022-11-23 09:13:38 +01:00
if ( flowContext . ContinuationMetadata = = null )
throw new InvalidOperationException ( "No ContinuationMetadata in FlowContext" ) ;
2021-12-10 11:45:09 +01:00
return flowProvider . SendRequest (
2023-04-20 10:40:13 +02:00
flowContext ,
message ,
responseHandlerInfo ,
flowContext . ContinuationMetadata . ConvergeMethodName ,
flowContext . ContinuationMetadata . ConvergeMethodSync ) ;
2021-12-09 23:52:25 +01:00
}
}
2017-01-31 12:01:08 +01:00
internal class ResponseHandlerInfo
{
2022-11-23 09:13:38 +01:00
public string MethodName { get ; }
public string ReplyToQueue { get ; }
public bool IsDurableQueue { get ; }
public ResponseHandlerInfo ( string methodName , string replyToQueue , bool isDurableQueue )
{
MethodName = methodName ;
ReplyToQueue = replyToQueue ;
IsDurableQueue = isDurableQueue ;
}
2017-01-31 12:01:08 +01:00
}
2023-04-20 10:40:13 +02:00
internal class PreparedRequest
{
public object Message { get ; }
public MessageProperties Properties { get ; }
public PreparedRequest ( object message , MessageProperties properties )
{
Message = message ;
Properties = properties ;
}
}
2017-01-31 12:01:08 +01:00
}
}