Events

Primavera.Core.Events contains types that provide an implementation of the publish-subscribe pattern for in-process and out-of-process server-side events.

This namespace uses Primavera.Hydrogen.EventBus as a dependency. The mentioned dependency is the successor of Primavera.Core.EventDriven.

Configuring the service

In order to adequately configure the event bus service, an element named eventBusConfiguration should be added to the host application web.config file.

The above-mentioned element should contain the configuration to both in-memory and Azure event bus.

The in-memory event bus is defined by the following attributes:

Attribute Description Optional?
parallelExecution Determines whether the in-memory event bus subscribers should be executed in parallel. No
enabled Determines whether the instance is enabled. Default is true. Yes

The Azure event bus contains the following attributes:

Attribute Description Optional?
name The name of the instance. No
enabled Determines whether the instance is enabled. Default is true. Yes
connectionString The Azure Service Bus instance connection string. No
autoComplete Flag that defines if a received event will be marked as processed as soon as it arrives to the client. No
maxConcurrentCalls The maximum number of concurrent calls to the Azure event bus instance. No
monitoringPath The Azure event bus instance path (i.e. topic) used for monitoring operations. No
eventTypeFilters Determines which type of events are published and subscribed through Azure event bus. All by default. Yes
<eventBusConfiguration>
    <elements>
      <add name="default">
        <inMemoryEventBus parallelExecution="true"/>
        <azureEventBus>
          <add name="default" connectionString="my-connection-string" maxConcurrentCalls="10" autoComplete="false" monitoringPath="t.elevationimportfileeventtopic" eventTypeFilters="ImportFileEvent"/>
        </azureEventBus>
      </add>
    </elements>
</eventBusConfiguration>

Invoking the service

The event bus service should be invoked thought the static singleton instance named EventBus.Default.

The respective singleton methods accept - as an argument - an EventBusStrategy enumerator that defines the event bus strategy to be used. If no argument is passed, EventBusStrategy.Auto will be used implicitly as a default strategy.

Publishing events

An event publishing operation involves the instantiation of a type that implements IEvent. A base implementation of IEvent should be inherited from EventBase when possible.

An attribute named EventBusPath can be used to specify the event bus target path. This attribute is particularly useful when publishing events to an out-of-process event broker. If no attribute is designated, the event will be published to a default path.

The event properties methods (mutators and accessors) should be defined in a specific manner in order to be properly mapped.

/// <summary>
/// Defines, for demonstration purposes, a custom event that transports an <see cref="IBusinessEntity"/>.
/// </summary>
/// <seealso cref="Primavera.Core.Events.EventBase"/>
public class MyEvent : EventBase
{
    /// <summary>
    /// Gets the business entity.
    /// </summary>
    public IBusinessEntity Entity
    {
        get
        {
            return this.GetValue<IBusinessEntity>(nameof(this.Entity));
        }

        private set
        {
            this.SetValue(nameof(this.Entity), value);
        }
    }
}

The respective IEvent instance should then be passed as an argument to the Publish or PublishAsync method of the EventBus.Default instance.

/// <summary>
/// Asynchronously called when an invoice is inserted.
/// </summary>
/// <param name="invoice">The inserted invoice.</param>
protected async Task OnInvoiceInsertedAsync(Invoice invoice)
{
    MyEvent @event = new MyEvent
    {
        Entity = invoice
    };

    await EventBus.Default().PublishAsync(@event);
}

Subscribing events

In order to subscribe to events of a given type, a concrete implementation of IEventSubscriber needs to be created.

The mentioned concrete type should implement a synchronous handler, an asynchronous handler or both. If both handlers are implemented, the synchronous handler takes precedence over the asynchronous handler.

/// <summary>
/// Defines a subscriber for <see cref="MyEvent"/>.
/// </summary>
/// <seealso cref="Primavera.Core.Events.IEventSubscriber{T}"/>
internal class MyEventSubscriber : IEventSubscriber<MyEvent>
{
    /// <summary>
    /// Gets the action that will handle the event synchronously.
    /// </summary>
    Action<MyEvent> IEventSubscriber<MyEvent>.HandleAction
    {
        get
        {
            return this.Handle;
        }
    }

    /// <summary>
    /// Gets the task that will handle the event asynchronously.
    /// </summary>
    Func<MyEvent, Task> IEventSubscriber<MyEvent>.HandleTask
    {
        get
        {
            return this.HandleAsync;
        }
    }

    /// <summary>
    /// Handles the event synchronously.
    /// </summary>
    /// <param name="event">The event.</param>
    private void Handle(MyEvent @event)
    {
        // My synchronous business logic with the event.
    }

    /// <summary>
    /// Handles the event asynchronously.
    /// </summary>
    /// <param name="event">The event.</param>
    private async Task HandleAsync(MyEvent @event)
    {
        // My asynchronous business logic with the event.
    }
}

The Subscribe or SubscribeAsync method of the EventBus.Default instance should then be called with the previously created IEventSubscriber instance as an argument.

/// <summary>
/// Registers <see cref="MyEvent"/> subscribers.
/// </summary>
private static void RegisterMyEventSubscribers()
{
    MyEventSubscriber subscriber = new MyEventSubscriber();

    EventBus.Default().Subscribe(subscriber);
}

The events whose type constraint have a match will then be transported to the registered subscriber.

The event bus adapter also supports subscribing to a set of native events. These events are triggered during CRUD operations over entities on the data layer and must be subscribed with the aid of a filter that complies with the IEventFilter contract.

Event type Description
EntityCreatedEvent Event triggered when a given entity is created. Should be subscribed through an EntityCreatedEventFilter.
EntityDeletedEvent Event triggered when a given entity is deleted. Should be subscribed through an EntityDeletedEventFilter.
EntityUpdatedEvent Event triggered when a given entity is updated. Should be subscribed through an EntityUpdatedEventFilter.
/// <summary>
/// Registers the billing module subscribers.
/// </summary>
private static void RegisterBillingSubscriber()
{
    BillingSubscriber subscriber = new BillingSubscriber();

    // Create a filter that subscribes only to events coming from the billing module
    // whose entities implement the IBusinessEntity contract, after they have been saved.
    EntityCreatedEventFilter filter = EntityCreatedEvent
        .Receive()
        .WhereModule("Billing")
        .WhereEntityAssignableTo<IBusinessEntity>()
        .AfterSaving();

    EventBus.Default().Subscribe(subscriber, filter);
}

Unsubscribing events

The unsubscribing operation is similar to the subscribing operation.

In order to unsubscribe from a given type of event, the Unsubscribe or UnsubscribeAsync method should be invoked with the respective IEventSubscriber and an optional IEventFilter as arguments.

/// <summary>
/// Unregisters the billing module subscribers.
/// </summary>
private static void UnregisterBillingSubscribers()
{
    BillingSubscriber subscriber = new BillingSubscriber();

    EntityCreatedEventFilter filter = EntityCreatedEvent
        .Receive()
        .WhereModule("Billing")
        .WhereEntityAssignableTo<IBusinessEntity>()
        .AfterSaving();

    EventBus.Default().Unsubscribe(subscriber, filter);
}

Remarks

  • The entity transported by a out-of-process event must be serializable and have no state attached.