public static void LogError(PollState ps, HttpChannelPoller.PollingError error) { switch (error) { case HttpChannelPoller.PollingError.UnableToConnect: { Logger.Warn($"Error GET {ps.NextUrl}: Unable to connect to api"); break; } case HttpChannelPoller.PollingError.UnknownErrorOnGet: { Logger.Error($"Error GET {ps.NextUrl}: Unknown error on get"); break; } case HttpChannelPoller.PollingError.ErrorMakingHttpRequest: { Logger.Error($"Error GET {ps.NextUrl}: making request"); break; } case HttpChannelPoller.PollingError.ErrorDeserializingContent: { Logger.Fatal($"Error GET {ps.NextUrl}: This is probably never going to work"); break; } } }
public static PollState ProcessTransportMessage( PollState pollStatus, TransportMessage transportMessage, Func <Type, Action <DomainMessageProcessingContext, object> > processors) { // Logger.Trace("---------------------------------------------------------"); // Logger.Trace($"PROCESSING TRANSPORT MESSAGE FOR: {pollStatus.MessageEndpointName}"); List <DomainMessage> unprocessedMessages = transportMessage .Messages .Where(x => (x.Header.MessageNumber) > pollStatus.LastMessageSuccessfullyProcessed) .OrderBy(x => x.Header.MessageNumber) .ToList(); Link prevLink = transportMessage.Header.Links.SingleOrDefault(l => l.Rel.Contains(Link.Previous)); bool areUnprocessedMessagesInEarlierTransportMessage = unprocessedMessages.Any() && prevLink != null && unprocessedMessages.Min(x => x.Header.MessageNumber) != pollStatus.LastMessageSuccessfullyProcessed + 1; if (areUnprocessedMessagesInEarlierTransportMessage) { return(pollStatus.With(prevLink.Href, 0, pollStatus.LastMessageSuccessfullyProcessed)); } var initialState = MessageEndpointContext.Start(pollStatus.MessageEndpointName); MessageEndpointContext ProcessMessageF(MessageEndpointContext context, DomainMessage domainMessage) => ProcessNext(context, domainMessage, processors); var finalState = unprocessedMessages.Aggregate( initialState, ProcessMessageF); var nextLink = transportMessage.Header.Links.SingleOrDefault(l => l.Rel.Contains(Link.Next)); var selfLink = transportMessage.Header.Links.Single(l => l.Rel.Contains(Link.Self)); var finishedWithThisTransportMessage = nextLink != null && unprocessedMessages.Count == finalState.ProcessedSuccessfully.Count; var newLastMessageNumberProcessed = finalState .ProcessedSuccessfully .Select(x => x.Header.MessageNumber) .Concat(new[] { pollStatus.LastMessageSuccessfullyProcessed }) .Max(); return(finishedWithThisTransportMessage ? pollStatus.With( nextLink.Href, PollState.NoDelay, newLastMessageNumberProcessed) : pollStatus.With( selfLink.Href, pollStatus.DefaultDelayMs, newLastMessageNumberProcessed)); }
// This is the entry point for an application to use this library. // Calling this function sets up the system to poll an endpoint, passing messages through the // relevant domainMessageProcessors. // Incidentally, it is essentially the last function in the receiving library to be non-functional, // what with its returning a Task (as good as void), and having a while loop that mutates the pollStatus // object. public static async Task MainInfinitePollerAsync( MessageEndpointState endpointState, Func <Type, Action <DomainMessageProcessingContext, object> > domainMessageProcessors, Func <string, Either <DeserializeError, TransportMessage> > deserializeTransportMessage, uint pollRateMs, Dictionary <HttpChannelPoller.PollingError, uint> pollingErrorDelays, Func <IHttpService> httpServiceCreator, CancellationToken ct) { using (IHttpService client = httpServiceCreator()) { while (!ct.IsCancellationRequested) { var channelLocation = endpointState.Channel; var uriBuilder = new UriBuilder( channelLocation.HttpChannelBase.Scheme, channelLocation.HttpChannelBase.Host, channelLocation.HttpChannelBase.Port) { Path = channelLocation.Path }; var startUrl = uriBuilder.ToString(); var pollStatus = new PollState(endpointState.EndpointName, pollRateMs, pollingErrorDelays, endpointState.LastSuccessfullyProcessedMessage, startUrl, 0); while (pollStatus.CanPoll()) { if (pollStatus.ShouldDelay()) { await Task.Delay((int)pollStatus.DelayMs, ct); } pollStatus = await Execute( pollStatus, client, ct, domainMessageProcessors, deserializeTransportMessage); } } } }
// The start of a largely functional code. // Given the current pollstate (ps), this function polls the channel through the 'client', // executing the relevant domainMessageProcessors for any new messages received, in order, // without processing duplicates. public static async Task <PollState> Execute( PollState ps, IHttpService client, CancellationToken ct, Func <Type, Action <DomainMessageProcessingContext, object> > domainMessageProcessors, Func <string, Either <DeserializeError, TransportMessage> > deserializeTransportMessage) { Either <HttpChannelPoller.PollingError, TransportMessage> transportMessage = await HttpChannelPoller.Poll(ps.NextUrl, client, ct, deserializeTransportMessage); PollState pollStatus = transportMessage.Match( error => { TransportMessageProcessor.LogError(ps, error); return(ps.WithDelayFor(error)); }, m => TransportMessageProcessor.ProcessTransportMessage( ps, m, domainMessageProcessors)); return(pollStatus); }