1
0
mirror of synced 2024-11-16 14:53:50 +00:00
Tapeti/Tapeti.Flow/Default/FlowProvider.cs

263 lines
9.6 KiB
C#

using System;
using System.Reflection;
using System.Threading.Tasks;
using RabbitMQ.Client.Framing;
using Tapeti.Config;
using Tapeti.Flow.Annotations;
using Tapeti.Flow.FlowHelpers;
namespace Tapeti.Flow.Default
{
public class FlowProvider : IFlowProvider, IFlowHandler
{
private readonly IConfig config;
private readonly IAdvancedPublisher publisher;
public FlowProvider(IConfig config, IPublisher publisher)
{
this.config = config;
this.publisher = (IAdvancedPublisher)publisher;
}
public IYieldPoint YieldWithRequest<TRequest, TResponse>(TRequest message, Func<TResponse, Task<IYieldPoint>> responseHandler)
{
var responseHandlerInfo = GetResponseHandlerInfo(config, message, responseHandler);
return new DelegateYieldPoint(true, context => SendRequest(context, message, responseHandlerInfo));
}
public IYieldPoint YieldWithRequestSync<TRequest, TResponse>(TRequest message, Func<TResponse, IYieldPoint> responseHandler)
{
var responseHandlerInfo = GetResponseHandlerInfo(config, message, responseHandler);
return new DelegateYieldPoint(true, context => SendRequest(context, message, responseHandlerInfo));
}
public IFlowParallelRequestBuilder YieldWithParallelRequest()
{
throw new NotImplementedException();
//return new ParallelRequestBuilder();
}
public IYieldPoint EndWithResponse<TResponse>(TResponse message)
{
return new DelegateYieldPoint(false, context => SendResponse(context, message));
}
public IYieldPoint End()
{
return new DelegateYieldPoint(false, EndFlow);
}
private async Task SendRequest(FlowContext context, object message, ResponseHandlerInfo responseHandlerInfo)
{
var continuationID = Guid.NewGuid();
context.FlowState.Continuations.Add(continuationID,
Newtonsoft.Json.JsonConvert.SerializeObject(new ContinuationMetadata
{
MethodName = responseHandlerInfo.MethodName,
ConvergeMethodName = null
}));
var properties = new BasicProperties
{
CorrelationId = continuationID.ToString(),
ReplyTo = responseHandlerInfo.ReplyToQueue
};
await publisher.Publish(message, properties);
}
private async Task SendResponse(FlowContext context, object message)
{
if (context.Reply == null)
throw new InvalidOperationException("No response is required");
if (message.GetType().FullName != context.Reply.ResponseTypeName)
throw new InvalidOperationException($"Flow must end with a response message of type {context.Reply.ResponseTypeName}, {message.GetType().FullName} was returned instead");
var properties = new BasicProperties
{
CorrelationId = context.Reply.CorrelationId
};
await publisher.PublishDirect(message, context.Reply.ReplyTo, properties);
}
private static Task EndFlow(FlowContext context)
{
if (context.Reply != null)
throw new InvalidOperationException($"Flow must end with a response message of type {context.Reply.ResponseTypeName}");
return Task.CompletedTask;
}
private static ResponseHandlerInfo GetResponseHandlerInfo(IConfig config, object request, Delegate responseHandler)
{
var binding = config.GetBinding(responseHandler);
if (binding == null)
throw new ArgumentException("responseHandler must be a registered message handler", nameof(responseHandler));
var requestAttribute = request.GetType().GetCustomAttribute<RequestAttribute>();
if (requestAttribute?.Response != null && requestAttribute.Response != binding.MessageClass)
throw new ArgumentException($"responseHandler must accept message of type {binding.MessageClass}", nameof(responseHandler));
return new ResponseHandlerInfo
{
MethodName = MethodSerializer.Serialize(responseHandler.Method),
ReplyToQueue = binding.QueueName
};
}
public async Task Execute(IMessageContext context, IYieldPoint yieldPoint)
{
var flowContext = (FlowContext)context.Items[ContextItems.FlowContext];
if (flowContext == null)
return;
var delegateYieldPoint = (DelegateYieldPoint)yieldPoint;
await delegateYieldPoint.Execute(flowContext);
if (delegateYieldPoint.StoreState)
{
flowContext.FlowState.Data = Newtonsoft.Json.JsonConvert.SerializeObject(context.Controller);
await flowContext.FlowStateLock.StoreFlowState(flowContext.FlowState);
}
else
{
await flowContext.FlowStateLock.DeleteFlowState();
}
}
/*
private class ParallelRequestBuilder : IFlowParallelRequestBuilder
{
internal class RequestInfo
{
public object Message { get; set; }
public ResponseHandlerInfo ResponseHandlerInfo { get; set; }
}
private readonly IConfig config;
private readonly IFlowStore flowStore;
private readonly Func<FlowContext, object, ResponseHandlerInfo, Task> sendRequest;
private readonly List<RequestInfo> requests = new List<RequestInfo>();
public ParallelRequestBuilder(IConfig config, IFlowStore flowStore, Func<FlowContext, object, ResponseHandlerInfo, Task> sendRequest)
{
this.config = config;
this.flowStore = flowStore;
this.sendRequest = sendRequest;
}
public IFlowParallelRequestBuilder AddRequest<TRequest, TResponse>(TRequest message, Func<TResponse, Task> responseHandler)
{
requests.Add(new RequestInfo
{
Message = message,
ResponseHandlerInfo = GetResponseHandlerInfo(config, message, responseHandler)
});
return this;
}
public IFlowParallelRequestBuilder AddRequestSync<TRequest, TResponse>(TRequest message, Action<TResponse> responseHandler)
{
requests.Add(new RequestInfo
{
Message = message,
ResponseHandlerInfo = GetResponseHandlerInfo(config, message, responseHandler)
});
return this;
}
public IYieldPoint Yield(Func<Task<IYieldPoint>> continuation)
{
return new YieldPoint(flowStore, true, context => Task.WhenAll(requests.Select(requestInfo => sendRequest(context, requestInfo.Message, requestInfo.ResponseHandlerInfo))));
}
public IYieldPoint Yield(Func<IYieldPoint> continuation)
{
return new YieldPoint(flowStore, true, context => Task.WhenAll(requests.Select(requestInfo => sendRequest(context, requestInfo.Message, requestInfo.ResponseHandlerInfo))));
}
}*/
internal class ResponseHandlerInfo
{
public string MethodName { get; set; }
public string ReplyToQueue { get; set; }
}
/*
* Handle response (correlationId known)
internal async Task HandleMessage(object message, string correlationID)
{
var continuationID = Guid.Parse(correlationID);
var flowStateID = await owner.flowStore.FindFlowStateID(continuationID);
if (!flowStateID.HasValue)
return;
using (flowStateLock = await owner.flowStore.LockFlowState(flowStateID.Value))
{
flowState = await flowStateLock.GetFlowState();
continuation = flowState.Continuations[continuationID];
if (continuation != null)
await HandleContinuation(message);
}
}
private async Task HandleContinuation(object message)
{
var flowMetaData = Newtonsoft.Json.JsonConvert.DeserializeObject<FlowMetaData>(flowState.MetaData);
var continuationMetaData =
Newtonsoft.Json.JsonConvert.DeserializeObject<ContinuationMetaData>(continuation.MetaData);
reply = flowMetaData.Reply;
controllerType = owner.GetControllerType(flowMetaData.ControllerTypeName);
method = controllerType.GetMethod(continuationMetaData.MethodName);
controller = owner.container.GetInstance(controllerType);
Newtonsoft.Json.JsonConvert.PopulateObject(flowState.Data, controller);
var yieldPoint = (AbstractYieldPoint) await owner.CallFlowController(controller, method, message);
await yieldPoint.Execute(this);
if (yieldPoint.Store)
{
flowState.Data = Newtonsoft.Json.JsonConvert.SerializeObject(controller);
flowState.Continuations.Remove(continuation);
await flowStateLock.StoreFlowState(flowState);
}
else
{
await flowStateLock.DeleteFlowState();
}
}
}
*/
}
}