A bit of history

Nowadays, modern software architecture moves more and more towards the cloud. Developers want to build resilient applications that can scale indefinitely. One of the techniques to achieve this is to use asynchronous messaging. These patterns are now well known and are battle-tested. However, one major issue remains. Everyone did their implementation with their enterprise standards, making interoperability between vendors almost impossible without a translation layer. That's why all the major vendors (Microsoft, Google, Amazon, Redhat, etc.) created a new industry standard called CloudEvent. This standard is also part of the Cloud Native Computing Foundation (CNCF). Everyone is now migrating toward this new format.

I've personally used it in many projects, and it works pretty well. However, the C# SDK never had what I wanted, and I found it quite hard to use without constantly looking at the documentation. It motivated me to create my abstractions on top of it to make it more user-friendly to C# developers.

Goals

  • Provide reasonable defaults, but make everything extensible
  • More userfriendly to use than the default C# SDK
  • Type safety to avoid runtime errors
  • Avoid confusion regarding the content of the Data property
  • Modern design using C# 9 records
  • Structural equality (Freebie with C# 9 records)
  • Intended to be used in your application's business/domain layer (carries all the metadata you need without the serialization information)
  • Very opinionated

Make it type-safe

One of the main goals was first to re-enforce the type safety of the CloudEvent object. I didn't like the fact that the Data property was of type object. It just makes everything hard to use later on in your business/domain layer as you need to cast it to the right type at runtime; Hello InvalidCastException.

One of the first things I implemented is CloudEvent<T>, which expose a type T version of the Data property. That class also enforces the CloudEvent spec with new types like NonEmptyString. These new types make sure that your CloudEvent is always valid at compile-time and helps the developer avoid common pitfalls. Making the Data property of type T also enables many compiler features like IntelliSense.

Examples

record UserCreated(string Name);

var userCreated = new UserCreated("Miguel");
var ce = new CloudEvent<UserCreated>(userCreated);
ce.Data.Name; // Name is now available with intellisense

Domain/Business layer

I like all the metadata carried with the CloudEvent. I find that those properties are beneficial even to perform business logic in some cases. Now that CloudEvent<T> is typed, why not use it in our domain layer. To do, so I decided to get rid of all the serialization information (Remember I said the lib was opinionated :P). That way, the CloudEvent<T> can be used in your domain layer as we removed all the infrastructure concepts, e.g., serialization and transport information. What does it mean in plain English?

  • Data is of type T, Data is always assumed to be deserialized
  • dataschema has been removed. Not needed as Data is assumed to be deserialized
  • dataContentType. Not needed as Data is assumed to be deserialized

In a nutshell

Until next time

This library won't fit everyone, and it's not its goal. It's a very opinionated way to use the cloud event standard in C#. Stay tuned as I'm planning on writing a few posts on the subject and show all the features and shortcuts I implemented in that library. In the meantime, any feedback or contribution is appreciated.

Resources