/// <summary> /// Converts the <see cref="SubscriptionOptionsDto"/> to a <see cref="StanSubscriptionOptions"/> /// </summary> /// <param name="subscriptionOptions">The <see cref="SubscriptionOptionsDto"/> to convert</param> /// <returns>The resulting <see cref="StanSubscriptionOptions"/></returns> public static StanSubscriptionOptions ToStanSubscriptionOptions(this SubscriptionOptionsDto subscriptionOptions) { StanSubscriptionOptions stanSubscriptionOptions = StanSubscriptionOptions.GetDefaultOptions(); stanSubscriptionOptions.ManualAcks = true; if (!string.IsNullOrWhiteSpace(subscriptionOptions.DurableName)) { stanSubscriptionOptions.DurableName = subscriptionOptions.DurableName; stanSubscriptionOptions.LeaveOpen = true; } if (subscriptionOptions.StreamPosition.HasValue) { if (subscriptionOptions.StreamPosition.Value == StreamPosition.Start) { stanSubscriptionOptions.StartWithLastReceived(); } else if (subscriptionOptions.StreamPosition.Value != StreamPosition.End && subscriptionOptions.StreamPosition.Value < 0) { throw new ArgumentOutOfRangeException(); } else { stanSubscriptionOptions.StartAt((ulong)subscriptionOptions.StreamPosition.Value); } } return(stanSubscriptionOptions); }
/// <inheritdoc/> public virtual async Task <IOperationResult <SubscriptionDto> > Handle(CreateSubscriptionCommand command, CancellationToken cancellationToken) { if (!this.ChannelManager.TryGetChannel(command.Channel, out IChannel channel)) { throw new OperationArgumentException($"Failed to find a channel with the specified name '{command.Channel}'", nameof(command.Channel)); } string subscriptionId = Guid.NewGuid().ToString(); SubscriptionOptionsDto subscriptionOptions = new SubscriptionOptionsDto() { Id = subscriptionId, Subject = command.Subject, Type = command.Type, Source = command.Source?.OriginalString, DurableName = command.Durable ? subscriptionId : null, StreamPosition = command.StreamPosition }; await channel.SubscribeAsync(subscriptionOptions, cancellationToken); this.SubscriptionManager.RegisterSubscription(subscriptionId, command.Subject, command.Type, command.Source, command.Channel, command.Subscribers); return(this.Ok(new SubscriptionDto() { Id = subscriptionId, Channel = command.Channel, Subject = command.Subject, Type = command.Type, Source = command.Source, Subscribers = command.Subscribers })); }
/// <summary> /// Creates the specified <see cref="Resources.Subscription"/> on the <see cref="Resources.Channel"/> it applies to /// </summary> /// <param name="subscription">The <see cref="Resources.Subscription"/> to create</param> /// <returns>A new awaitable <see cref="Task"/></returns> protected virtual async Task SubscribeToChannelAsync(Resources.Subscription subscription) { if (!this.ChannelManager.TryGetChannel(subscription.Spec.Channel, out IChannel channel)) { return; } if (string.IsNullOrWhiteSpace(subscription.Spec.Id)) { try { this.Logger.LogInformation("Updating the subscription with name '{resourceName}'...", subscription.Name()); subscription.Spec.Id = Guid.NewGuid().ToString(); await this.KubernetesClient.ReplaceNamespacedCustomObjectAsync(subscription, subscription.ApiGroup(), subscription.ApiGroupVersion(), subscription.Namespace(), SubscriptionDefinition.PLURAL, subscription.Name()); this.Logger.LogInformation("The subscription with name '{resourceName}' has been successfully updated", subscription.Name()); } catch (HttpOperationException ex) { this.Logger.LogError($"An error occured while updating the status of the CRD '{{resourceKind}}' with name '{{resourceName}}': the server responded with a non-success status code '{{statusCode}}'.{Environment.NewLine}Details: {{responseContent}}", subscription.Kind, subscription.Name(), ex.Response.StatusCode, ex.Response.Content); } } SubscriptionOptionsDto subscriptionOptions = new SubscriptionOptionsDto() { Id = subscription.Spec.Id, DurableName = subscription.Spec.IsDurable ? subscription.Spec.Id : null, Subject = subscription.Spec.Subject, Source = subscription.Spec.Source?.OriginalString, Type = subscription.Spec.Type, StreamPosition = subscription.Spec.Position }; await channel.SubscribeAsync(subscriptionOptions); this.RegisterSubscription(subscription); }
/// <inheritdoc/> public virtual async Task InitializeAsync(CancellationToken cancellationToken = default) { foreach (KeyValuePair <string, string> entry in this.SubscriptionRegistry) { SubscriptionOptionsDto subscriptionOptions = JsonConvert.DeserializeObject <SubscriptionOptionsDto>(entry.Value); await this.SubscribeAsync(subscriptionOptions, false, cancellationToken); } await Task.CompletedTask; }
/// <inheritdoc/> public virtual async Task SubscribeAsync(SubscriptionOptionsDto subscriptionOptions, CancellationToken cancellationToken = default) { this.Logger.LogInformation("Creating a new subscription with id '{subscriptionId}' on channel '{channelName}'...", subscriptionOptions.Id, this.Name); string json = JsonConvert.SerializeObject(subscriptionOptions); using (HttpResponseMessage response = await this.HttpClient.PostAsync("sub", new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json))) { string responseContent = await response.Content?.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { this.Logger.LogError($"An error occured while subscribing to the channel '{{channel}}': the remote server responded with a '{{statusCode}}' status code.{Environment.NewLine}Details: {{responseContent}}", this.Name, response.StatusCode, responseContent); } } this.Logger.LogInformation("A new subscription with id '{subscriptionId}' has been succesfully created on channel '{channelName}'", subscriptionOptions.Id, this.Name); }
/// <summary> /// Creates a new subscription /// </summary> /// <param name="subscriptionOptions">The object used to configure the subscription to create</param> /// <param name="persist">A boolean indicating whether or not to persist the subscription to the <see cref="SubscriptionRegistry"/></param> /// <param name="cancellationToken">A <see cref="CancellationToken"/></param> /// <returns>A new awaitable <see cref="Task"/></returns> protected virtual async Task SubscribeAsync(SubscriptionOptionsDto subscriptionOptions, bool persist, CancellationToken cancellationToken = default) { string subject = this.GetStanSubjectFor(subscriptionOptions.Subject); this.Logger.LogInformation("NATSS subject: {subject}", subject); IStanSubscription subscription = this.StanConnection.Subscribe(subject, subscriptionOptions.ToStanSubscriptionOptions(), this.CreateEventHandlerFor(subscriptionOptions.Id)); this.Subscriptions.TryAdd(subscriptionOptions.Id, subscription); if (persist) { lock (this._Lock) { this.SubscriptionRegistry.InsertKey(subscriptionOptions.Id, JsonConvert.SerializeObject(subscriptionOptions), false); } } await Task.CompletedTask; }
/// <summary> /// Gets the id of the stream the specified <see cref="SubscriptionOptionsDto"/> applies to /// </summary> /// <param name="subscriptionOptions">The <see cref="SubscriptionOptionsDto"/> to get the stream id for</param> /// <returns>The id of the stream the specified <see cref="SubscriptionOptionsDto"/> applies to</returns> protected virtual string GetEventStreamIdFor(SubscriptionOptionsDto subscriptionOptions) { if (!string.IsNullOrWhiteSpace(subscriptionOptions.Subject)) { return($"$cloudevent-subject-{subscriptionOptions.Subject}"); } else if (!string.IsNullOrWhiteSpace(subscriptionOptions.Type)) { return($"$et-{subscriptionOptions.Type}"); } else if (!string.IsNullOrWhiteSpace(subscriptionOptions.Source)) { return($"$cloudevent-source-{subscriptionOptions.Source}"); } else { throw new NotSupportedException(); } }
/// <summary> /// Creates a new subscription /// </summary> /// <param name="subscriptionOptions">The object used to configure the subscription to create</param> /// <param name="persist">A boolean indicating whether or not to persist the subscription to the <see cref="SubscriptionRegistry"/></param> /// <param name="cancellationToken">A <see cref="CancellationToken"/></param> /// <returns>A new awaitable <see cref="Task"/></returns> protected virtual async Task SubscribeAsync(SubscriptionOptionsDto subscriptionOptions, bool persist, CancellationToken cancellationToken = default) { string streamId = this.GetEventStreamIdFor(subscriptionOptions); object subscriptionSource; if (string.IsNullOrWhiteSpace(subscriptionOptions.DurableName)) { if (subscriptionOptions.StreamPosition.HasValue) { CatchUpSubscriptionSettings settings = subscriptionOptions.ToCatchUpSubscriptionSettings(); subscriptionSource = this.EventStoreConnection.SubscribeToStreamFrom(streamId, subscriptionOptions.StreamPosition.Value, settings, this.CreateCatchUpSubscriptionHandler(subscriptionOptions.Id), subscriptionDropped: this.CreateCatchUpSubscriptionDropHandler(subscriptionOptions.Id, streamId, subscriptionOptions.StreamPosition, settings)); } else { subscriptionSource = await this.EventStoreConnection.SubscribeToStreamAsync(streamId, true, this.CreateStandardSubscriptionHandler(subscriptionOptions.Id), subscriptionDropped : this.CreateStandardSubscriptionDropHandler(subscriptionOptions.Id, streamId)); } } else { try { await this.EventStoreConnection.CreatePersistentSubscriptionAsync(streamId, subscriptionOptions.DurableName, subscriptionOptions.ToPersistentSubscriptionSettings(), new UserCredentials(this.ApplicationOptions.EventStore.Username, this.ApplicationOptions.EventStore.Password)); } catch { } subscriptionSource = await this.EventStoreConnection.ConnectToPersistentSubscriptionAsync(streamId, subscriptionOptions.DurableName, this.CreatePersistentSubscriptionHandler(subscriptionOptions.Id), this.CreatePersistentSubscriptionDropHandler(subscriptionOptions.Id, streamId, subscriptionOptions.DurableName), autoAck : false); } this.AddOrUpdateSubscription(subscriptionOptions.Id, streamId, subscriptionOptions.DurableName, subscriptionSource); if (persist) { lock (this._Lock) { this.SubscriptionRegistry.InsertKey(subscriptionOptions.Id, JsonConvert.SerializeObject(subscriptionOptions), false); } } this.Logger.LogInformation("Created a new EventStore subscription to stream '{streamId}'", streamId); }
public async Task <IActionResult> Subscribe([FromBody] SubscriptionOptionsDto subscription) { return(this.Process(await this.Mediator.Send(new SubscribeCommand(subscription)), (int)HttpStatusCode.Accepted)); }
/// <summary> /// Initializes a new <see cref="SubscribeCommand"/> /// </summary> /// <param name="subscriptionOptions">The options used to configure the subscription to create</param> public SubscribeCommand(SubscriptionOptionsDto subscriptionOptions) { this.SubscriptionOptions = subscriptionOptions; }
/// <summary> /// Transforms the <see cref="SubscriptionOptionsDto"/> into a new <see cref="CatchUpSubscriptionSettings"/> /// </summary> /// <param name="subscriptionOptions">The <see cref="SubscriptionOptionsDto"/> to transform</param> /// <returns>A new <see cref="CatchUpSubscriptionSettings"/></returns> public static CatchUpSubscriptionSettings ToCatchUpSubscriptionSettings(this SubscriptionOptionsDto subscriptionOptions) { CatchUpSubscriptionSettings defaultSettings = CatchUpSubscriptionSettings.Default; return(new CatchUpSubscriptionSettings(defaultSettings.MaxLiveQueueSize, defaultSettings.ReadBatchSize, defaultSettings.VerboseLogging, true)); }
/// <summary> /// Transforms the <see cref="SubscriptionOptionsDto"/> into a new <see cref="PersistentSubscriptionSettings"/> /// </summary> /// <param name="subscriptionOptions">The <see cref="SubscriptionOptionsDto"/> to transform</param> /// <returns>A new <see cref="PersistentSubscriptionSettings"/></returns> public static PersistentSubscriptionSettings ToPersistentSubscriptionSettings(this SubscriptionOptionsDto subscriptionOptions) { PersistentSubscriptionSettingsBuilder builder = PersistentSubscriptionSettings.Create(); if (subscriptionOptions.StreamPosition.HasValue) { switch (subscriptionOptions.StreamPosition.Value) { case StreamPosition.Start: builder.StartFromBeginning(); break; case StreamPosition.End: builder.StartFromCurrent(); break; default: builder.StartFrom(subscriptionOptions.StreamPosition.Value); break; } } builder.ResolveLinkTos(); builder.WithMaxSubscriberCountOf(1); builder.MinimumCheckPointCountOf(1); builder.MaximumCheckPointCountOf(1); return(builder.Build()); }
/// <inheritdoc/> public virtual async Task SubscribeAsync(SubscriptionOptionsDto subscriptionOptions, CancellationToken cancellationToken = default) { await this.SubscribeAsync(subscriptionOptions, true, cancellationToken); }