Securing Sensitive Data in an Event Driven Architecture

You secure sensitive data when stored in a database, but are you securing sensitive data in an event or message driven architecture? For example, if you need to include Credit Card information in a message for a queue to be processed. But need to be compliant with PCI-DSS! You can encrypt the actual message or a portion of the message, or if the broker supports it, use server-side encryption.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Transfering Senstive Data

When not using a message broker and doing everything in a synchronous request/response, we can simply use a secure transport such as HTTPS/TLS. As an example, if we had a checkout process where we needed to process a credit card, we would have the browser send the data over HTTPS/TLS to our HTTP API service, and then it would, in turn, make an HTTPS/TLS request to the Payment Gateway we’re using to process credit card transactions.

Our HTTP API just becomes a proxy to relay that information to our payment gateway. At no point are we storing that data on any type of durable storage where it will be left unencrypted in plain text. Everything is secured transferring it from our Client/Browser to our HTTP and then finally to the payment gateway all over HTTPS/TLS.

Securing Sensitive Data At Rest

This works fine until you actually need to persist sensitive data, or in this example, credit card info. It may not be ideal to be bound to process the credit card via the Payment Gateway when we place an order. If the payment gateway is unavailable or causes an error, we will lose the order.

Rather what we can do is place the order and then send a message to a queue to process the credit card asynchronously in a separate process.

This decouples the two actions of placing an order and processing the credit card.

So what’s the issue? It may not seem obvious, because we are well aware of securing sensitive data in a database, however, in order to do this, we must place the credit card information inside our message onto the queue, which is likely using durable storage. Now we have sensitive data that are unencrypted in plain text. Not ideal.

Encrypted Message (or Properties)

One solution to this is to encrypt the message or properties within the message before they are actually sent to the broker. When the client sends the data over HTTPS/TLS, we then create our message encrypt it (or properties within the message)

Securing Sensitive Data in an Event Driven Architecture

Then our consumer can pick up the encrypted message from the message broker, decrypt it locally in memory and get out the values.

Securing Sensitive Data in an Event Driven Architecture

This requires the producer and consumer to share a key that is used for encryption and decryption.

As an example, NServiceBus enables securing sensitive data by encrypting properties within a message. You can configure this by using the EnableMessagePropertyEncryption on the endpoint configuration.

Then it’s as seamless as using the EncryptedString type in any message. NServiceBus will handle the encryption and decryption automatically.

Here’s a screenshot of a PlaceOrder command using RabbitMQ. You can see the CreditCard Number has an EncryptedValue. Everything is stored in rabbit as an encrypted value and nothing is in plain text.

Server Side Encryption

Another option is using a message broker that supports server-side encryption (AWS SQS, Azure Service Bus). This means that all messages are encrypted at rest by the broker itself.

This means that nothing has to change in your application in terms of encrypting messages. We send the message broker our messages in plain text.

Securing Sensitive Data in an Event Driven Architecture

However, the broker itself is encrypting the messages while at rest.

Securing Sensitive Data in an Event Driven Architecture

When a consumer receives a message, the broker will unencrypt it and send the consumer the message in plain text.

Securing Sensitive Data in an Event Driven Architecture

All communication is done over secure channels.

Securing Sensitive Data

Often times messages are overlooked or not thought of as persistent storage or somewhere to think about having to secure sensitive data. If you have requirements to store sensitive data for various compliance reasons, make sure to secure messages either by encrypting the message at self prior to sending it to the broker or by leveraging a broker that supports encryption at rest.

Source Code

Developer-level members of my YouTube channel or Patreon get access to the full source for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.

Related Links

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Should you publish Domain Events or Integration Events?

Common advice is to not publish domain events outside of your service boundary. They should only exist within your service boundary. Instead, you should publish integration events for other service boundaries. While this general advice makes sense, it’s not so cut-and-dry. There are many reasons why you would want to publish domain events for other services to consume. Here are how I think of Domain Events and Integration Events and when to use them.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Domain Events

What most people are referring to and generally implement when they talk about Domain Events is within a single boundary. When a state change or something occurs a domain event is published that is then processed by consumers within the same boundary.

This is generally all done in-process. Meaning the consumers all process the same event within the same process as the publisher. There is no message broker or any asynchronous messaging occurring. Everything is done in-memory and in-process. Because of this, this can often be wrapped within the same database transaction. If a consumer throws an exception, it will go up the call stack to the publisher, which can then roll back the transaction.

While this seems on the surface like a good pattern it can oftentimes be less desirable than actually having your consumers in isolation and moving the consumer processing out of process. More on that in a future video/blog post.

Integration Events

Integration events are generally used for integrating with other service boundaries. This then means we’re moving work out of process by leveraging a message broker. Consumers will each individually process the event in isolation without having any effect on the publisher or any other consumers.

Integration events differ from Domain Events in that Domain Events are very specific concepts within a boundary. A domain event may not mean anything or have a perceived different meaning to another boundary. Integration Events are specifically for telling other outside boundaries that something has occurred within a boundary.

Inside vs Outside Events

Generally, Domain Events will be referred to as “Inside Events” because they don’t leave their boundary. While Integration Events are referred to as “Outside Events” because their intent is to leave their boundary.

Why do people recommend not exposing domain events to other boundaries but rather exposing integration events instead? While that recommendation has a good intent, it’s a bit misleading. For me, you can expose domain events outside your boundary if it fits these 3 requirements: Stability, Understanding, and Consumer Requirements.

Stability

Events that are stable consider exposing outside your own boundary.

If you’re talking with the business and domain experts are collaboratively determining the various domain events that are a part of a specific boundary, then they are likely to be stable business concepts.

If the business concepts are stable, then your events will be stable and they won’t be likely to change. This is the primary reason why people advocate for Integration Events. Once you expose an event to outside consumers, you have to version them just as you would any API changes that are public.

If you’re using stable business concepts then they aren’t likely to change. Domain Events aren’t for data propagation but rather to indicate what has occurred. This is often very useful for a long-running business process where many different boundaries are involved. Domain events are more behavioral rather than derived from CRUD.

Understanding

A service boundary can be a linguistic boundary. One concept in one boundary can have a very different meaning in another boundary. Because of this, a domain event in one boundary might not mean anything, or worst have a different perceived meaning in another boundary.

If you’re exposing domain events to other boundaries then there needs to be a clear understanding of what those events are from other boundaries. There must be a level of shared understanding. As mentioned, this often occurs when domain events are used as a way to notify various boundaries within a system that is all a part of a long-running business process.

Consumer Requirements

While the benefit of an event-driven architecture is to decouple producers and consumers, in practice you do actually care about what the consumer requirements are.

There are different purposes for events. Mainly for notification or data propagation. Consumers of events are going to care about events for these two specific reasons.

If they want to consume an event because of data propagation it’s because they want to keep a local cache copy of data that is owned from another boundary. These types of events will often contain a lot more data and often be “fat events” or referred to as Event Carried State Transfer.

If the service wants to consume an event because it’s a part of a long-running business process, then it doesn’t really care about the state so much as it does about simply being notified that an event has occurred, and now it must react and do its part of the business process.

For data propagation, events are often derived from CRUD or Property-based events. For example, ProductUpdated.

While events used for notifications are generally more behavioral. For example, ProductInventoryAdjusted.

Domain Events or Integration Events?

As always, it depends. If your domain events are stable business concepts and they are understood outside of your boundary as a part of a long-running business process, then yes, publishing domain events outside of your boundary are acceptable.  If events are used for data propagation or are more CRUD in nature, then publish Integration Events.

Source Code

Developer-level members of my YouTube channel or Patreon get access to the full source for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.

Related Links

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Publishing Events from CRUD or Commands?

There are many different ways to create events in Event Driven Architecture. Events from CRUD or created from specific actions that have occurred. In some cases, consumers only care about the full scope of an entity/data. In other boundaries, consumers only care about what has occurred. When publishing events from CRUD you’ll likely care more about propagating state. However, events derived from tasks/commands are often used more as notifications about business process occurrences.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

CRUD

When developers start getting into Event Drive Architecture, I find they most often create events that are represented by Create/Update/Delete of an Entity. Meaning if there is a Product Entity, then they have a ProductCreated event, ProductUpdated event, or ProductDeleted event.

This is most often driven by UI and APIs that are CRUD and Entity oriented. I call these APIs Entity Services and try to avoid them when working in the core of a domain. For more info on Entity Services, check out my post AVOID Entity Services by Focusing on Capabilities

The next most common type of event created for those new to Event Driven Architecture is still driven by CRUD and Entities, but all the updates are property-based. If the Product Entity has a Quantity property, then the event published ends up being a ProductQuantityUpdated event.

In both types of events what’s common about them is the event contains data. If it’s a Created or Update type event, often it will contain the entire Entity that was created. If it’s a Property Updated event, it will likely contain the new value of the property, but it’s also common that people will put the old value as well.

The reason why this is common is that a lot of systems are developed primarily based on CRUD. If you’re UI and APIs are driven by CRUD, you’re going to develop events that are derived from CRUD.

Behavioral

What’s the alternative to CRUD? Taking a more task-driven approach to developing a system. Providing end-users with capabilities that are directly related to the business functions they perform. In my example above with ha Product Entity, a user doesn’t “Update the Quantity”. There is a reason for this that’s related to a task. Did the warehouse do a stock count and realized they found more products on the actual shelves than are stated in the system? If so they would perform an “Inventory Adjustment”. There’s a difference between “Inventory Adjustment” and “Update the Quantity”.

In the events above, they are all behavioral. These events are statements of facts of actual business events. In order to publish these types of events, you need to move away from CRUD and move towards Commands and a Task Based-UI. For more on this check out my post Decomposing CRUD to a Task Based UI.

In the above screenshot, we’ve moved away from CRUD in some aspects. The Name and Description can be saved by a simple update however we can no longer just change the Quantity. We need to specify an action/task that is a business reason why we are changing the Quantity.

From this, we can derive that we are creating and publishing an InventoryAdjusted event which allows us to be much more explicit.

Explicit vs Implicit

CRUD-based events (ProductCreated, ProductUpdated, etc) aren’t inherently bad. They can be useful for referential data that lives on the edge of your system. Data that is not a part of the (sub)domain you’re focusing on. For this reason, you might publish CRUD-based type events because other service boundaries want a local cache copy of that data. However, since they aren’t explicit about the reason or business task that occurred and created the event, you won’t have that insight from a consumer. You would have to imply it, if you could, based on data that changed.

Behavioral Events (ProductShipped, ProductInventoryAdjusted, etc) are explicit. They tell consumers exactly what has occurred. These types of events are generally used as a notification to other service boundaries that might be a part of a long-running business process. They usually only contain a minimal amount of data. They are generally used for data propagation for local cache copies. Check out my post on Event Choreography & Orchestration that covers how behavioral events are used between services.

Source Code

Developer-level members of my CodeOpinion YouTube channel get access to the full source for any working demo application that I post on my blog or YouTube. Check out the membership for more info.

Related Links

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design