Pub/Sub Design Pattern in .NET Distributed Cache
The publish-Subscribe pattern also known as Pub/Sub is an indispensable tool for building enterprise-grade .NET applications. Just to refresh your memory, Pub/Sub is a messaging paradigm where the senders of messages (publisher) do not have any knowledge about the intended recipients (subscribers). Moreover, the publisher and subscriber applications do not interact with each other directly but instead depend on a common medium known as a topic. Hence, it's a loosely coupled messaging model.
Now, assume that you have multiple applications of different roles deployed within the same architecture and they need a mechanism to inform/notify each other about certain events. These events could either be transient (due to changes made on the run time) or database events (due to changes in the database). That's exactly where the publish-subscribe design pattern will help you to enable distributed events.
Enabling Distributed Events
To design a distributed event-driven architecture, developers traditionally turn towards using either of the following methods below.
Data Notifications provided by RDBMS
If the datastore is limited to a relational database, using the database notification feature seems to be the best available option. It allows you to register your interest with the database server and notifies your applications when there is any change in the database result set due to update, add, or delete.
However, RDBMS are intrinsically unscalable and easily become a performance bottleneck in applications. You do not want to put an unnecessary load on your databases. Besides, the database notifications feature itself is inherently slow and doesn't support runtime data sharing as well.
Now you may understand why using the database as a messaging medium cannot be the best design choice for your applications.
The other option you have is to introduce a separate messaging queue in your architecture. While these messaging queues do a great job in helping you transmit messages among applications, these queues are not data-focused i.e. they do not monitor data changes in the databases or any other source. Also, these messaging queues cannot scale alongside your application tier.
Implement a Custom Solution
The last option left for you is to build your messaging platform/medium to suit your need. While this empty sandbox is very tempting at first, allowing you to develop your stuff however you want, there is complexity in terms of time and resources required. Although possible, building and managing a robust and scalable messaging platform is a very daunting task.
Now, the question remains which solution is easy to incorporate, scalable, highly available, and also very reliable?
A Distributed Cache as a Messaging Platform
Fret not, there is an easy solution. A more modern way to incorporate a robust messaging platform is to use an in-memory distributed cache. NCache is the only truly native .NET/.NET Core distributed cache available in the market. It is an in-memory distributed cache that is extremely fast and scalable. It enables your applications to handle extreme transaction loads without your database becoming a bottleneck.
NCache is usually deployed in the middle tier of your n-tier architecture. Take a look at the following diagram for a better understanding.
NCache is a cluster of cache servers serving tens of thousands of requests per second for your .NET applications as well as Java apps by keeping frequently used data in memory and helps avoid the majority of the database trips.
Let’s first see how NCache is inherently suitable to act as a messaging bus with its event-driven architecture.
NCache Event-Driven Messaging
The following figure shows how NCache acts as a messaging bus for .NET and Java applications.
Here NCache enables cross-platform communication by employing fast compact serialization, which converts your .NET or Java objects to binaries and then transfers them over to the cache cluster. So, this is how NCache allows your .NET applications to interact with Java applications and vice versa. For more information, take a look at portable data types.
NCache manages Pub/Sub design patterns under the name of Events and provides you with different methods to propagate your messages to other listening applications. Let’s take a look at both message types and see how a distributed cache can propagate them.
First, consider the data changes your applications need to listen to. Since NCache is also a .NET key-value store, it provides you with features to update your applications in case of any data changes within the cache. Because all of this is in-memory therefore there is no performance bottleneck. These updates could either be;
- Cache level item changes, be it update or remove.
- Whole cache level data changes.
- Continuous Query, where you register an SQL-like query to be watchful for if the result set changes within the cache. If it does your applications are notified.
- Cluster changes, if any new node is added or removed, or crashed. (For administration).
NCache also allows you to register dependency on databases including SQL, Oracle, and OleDb. This helps to keep your cache up to date with the database and thus your applications. For a full list of supported dependency types, check the database dependency. These dependencies are data change notifications registered with different data stores but you let NCache handle it. You can also combine database notifications with NCache data notifications to enrich your specific use case.
On the other hand, if you want to just propagate simple messages to complex .NET or Java objects then you should use the custom messaging feature. Here one application can produce the data and fire an event, whereas the interested listeners will receive the event almost instantaneously. For further information in this regard, you can see custom events.
NCache Pub/Sub API
NCache provides an in-memory Publisher/Subscriber (Pub/Sub) feature and a dedicated Pub/Sub Messaging store to enable real-time information sharing in .NET web applications. We will see how combining NCache and Pub/Sub can address the above challenges and eventually help your applications communicate better.
The Pub/Sub model naturally decouples publishers and subscribers by providing a channel where messages are published and subscribed by the interested users. Now when NCache acts as a messaging bus, the Pub/Sub model benefits from the underlying NCache distributed architecture and numerous handy features.
Without further ado, let's first get to know the basic components of NCache Pub/Sub and their working. The general flow of the NCache Pub/Sub messaging is like this - the publisher publishes messages on a topic using the ITopicinterface. The subscribers create subscriptions to one or many topics and receive relevant messages. On successful message delivery, NCache receives the acknowledgment. Otherwise, NCache keeps retrying before the message expires (if expiration is set). Undelivered messages reside in the cache until eviction or expiration are triggered and enabled.
NCache provides two different types of subscriptions namely, non-durable and durable subscriptions for pub/sun messaging. Furthermore, NCache enables exclusive and shared subscription policies. The relevant details are discussed below.
- Non-Durable Subscription: By default, all subscriptions created on a topic are non-durable subscriptions. It conveys intended messages to the subscriber-only until it remains connected. In case the subscriber’s connection is lost due to any reason, it doesn’t receive old messages on rejoining the network. This type of subscription is exclusive, which means that one subscription belongs to one subscriber.
- Durable Subscription: It takes account of a message loss on subscriber disconnectivity. NCache retains the subscription of a subscriber on connection loss. Hence, a subscriber can receive the published messages of interest on reconnecting. A durable subscription offers two policies:
- Exclusive: One subscription belongs to one active subscriber at a time.
- Shared: One subscription can have more than one subscriber registered at a time and the load is shared.
Getting Started with NCahce Pub/Sub
Let’s assume that there is an e-commerce store where new products are added to the store on regular basis by different vendors. Meanwhile, sales and offers are also provided on the products. The store managers and customers interested in the offered products need to stay updated about new products, products on sale, and discounts. NCache Pub/Sub feature can enable distributed notification system in this scenario. For that, NCache dedicated Pub/Sub Messaging store can be created first.
Let’s discuss the step-by-step process of achieving distributed messaging in the above scenario by using NCache Pub/Sub Messaging API.
Create a Topic
As a first step, a topic needs to be created where updates on new products can be published by different vendors. A new topic with a unique name can be created using the NCache ITopic interface. Here is how the publisher application uses the method CreateTopic to create a topic with the name
// Pre-condition: Cache is already connected // Specify the topic name string topicName = “newProducts” // Create topic ITopic topic = cache.MessagingService.CreateTopic(topicName);
In case a topic with the provided name already exists, an instance of the topic is returned as ITopic.
NCache allows to set topic priority while creating the topic. It is useful when certain events need to be communicated on a higher priority than others. For instance, the information about finished goods is urgent. Similarly, the update in product prices due to discount or sale can be most important being a seller/buyer. In that case, the topic priority can be set to high while creating it, and the relevant notifications can be received without any delay.
Once a topic is created, the publisher application can publish relevant messages to that topic using Publish method. For that, an instance of the topic is first obtained by specifying the topic name. NCache provides the following two delivery modes while publishing messages:
- All (default): Delivers the message to all the registered subscribers. It is useful when the information needs to be broadcasted.
- Any: Delivers the message to any of the registered subscribers.
Furthermore, to efficiently manage the storage space of your Pub/Sub cache, you can also set expirations for messages.
In the following code, messages about new products are broadcasted by a publisher on an already existing topic
// Pre-condition: Cache is already connected // Topic “newProducts” already created string topicName = “newProducts” // Get the topic ITopic productTopic = cache.MessagingService.GetTopic(topicName); // Create the object to be sent in message Product product = FetchProductFromDB(10248); // Create the new message with the object order var productMessage = new Message(product); // Publish the order with delivery option set as all orderTopic.Publish(productMessage, DeliveryOption.All, true);
Subscribe for Topic Messages
Once the topic is created, a subscriber application can receive the messages published to that topic by getting a subscription. Since different types of subscriptions are supported, a subscriber interested in a non-durable subscription can use the
CreateSubscription method. For durable subscriptions, the
CreateDurableSubscription method can be used.
The code below shows how a subscriber application can create a subscription for the topic
MessageReceived the callback is registered to perform any intended operation on being notified. For instance, a subscriber can update product prices in the
MessageReceived callback on receiving a sale notification.
// Pre-condition: Cache is already connected // Topic “newProducts” already created string topicName = “newProducts” // Get the topic ITopic productTopic = cache.MessagingService.GetTopic(topicName); // Create and register subscribers for topic newProducts // MessageReceived callback is specified ITopicSubscription orderSubscriber = orderTopic.CreateSubscription(MessageReceived);
In the above example, a non-durable subscription is created. Besides, a subscriber can create a durable subscription when there is a need to receive the old messages from subscribed topic/topics on reconnection.
NCache also provides a pattern-based method of subscription where NCache supports multiple wildcards to subscribe for single/multiple topics falling under the provided pattern.
NCache empowers the publishers to know the status of messages and the availability of topics. The following notifications can be registered by the publisher applications:
- MessageDeliveryFailure: Notifies the publisher if a message has failed to deliver because of any issue.
- OnTopicDeleted: Notifies a publisher when a message gets deleted.
Here is how simply the publisher can register for these two types of notifications.
// You have an instance productTopic of existing topic // Register message delivery failure productTopic.MessageDeliveryFailure += OnFailureMessageReceived; //Register topic deletion notification productTopic.OnTopicDeleted = TopicDeleted;
By following the above steps a basic Pub/Sub Messaging architecture can be integrated into any ASP.NET/.NET Core application.
To this point, you are familiar with the NCache Pub/Sub feature. Let’s summarize what benefits Ncache Pub/Sub offers to handle the limitations of existing solutions.
- Due to the linear scalability, NCache Pub/Sub can handle the increasing number of subscription requests by adding cache servers and performing load balancing on the go. The scaling up occurs transparently to the users without hindering the communication process. Hence, you can easily scale up your communication performance using NCache Pub/Sub.
- NCache Pub/Sub facilitates durable subscriptions, message delivery retires and delivery failure notifications to avoid message loss. Moreover, the distributed and replicated architecture of NCache ensures the high availability of NCache that accommodates subscribers' connectivity in dynamic environments. Altogether these features ensure reliable communication.
- Since NCache is an in-memory distributed cache, the message store residing in the cache is inherently fast. In addition, NCache allows expiration and evictions on items residing in the cache to intelligently manage the storage space.
The scalability, reliability, and storage efficiency of NCache together with loose coupling and async messaging mode of Pub/Sub make NCache Pub/Sub feature highly promising for distributed messaging in the future .NET/.NET Core applications.