Skip to content

v4.0.0-preview1

Pre-release
Pre-release
Compare
Choose a tag to compare
@ReubenBond ReubenBond released this 16 Feb 00:02
dc036e3

Major changes from the 3.x release series

The full change log lists around 500 changes, and many involve small quality of life improvements, internal refactorings for performance, reliability, versatility, and maintainability. What follows are the larger changes which we would like to highlight in these release notes since they are more likely to impact application developers.

Grain and Stream identity enhancements

Grain identities now take the form type/key where both type and key are strings. This greatly simplifies how grain identity works and improves support for generic grain types.
Previous versions of Orleans used a compound type for GrainIds in order to support grain keys of either: Guid, long, string, Guid + string, or long + string. This involves some complexity when it comes to dealing with grain keys. Grain identities consist of two components: a type and a key. The type component previously consisted of a numeric type code, a category, and 3 bytes of generic type information.

Streams are also now identified using a string instead of a Guid, which grants more flexibility, especially when working with declarative subscriptions (most commonly accessed via the [ImplicitStreamSubscription(...)] attribute)

This move to stringly-typed ids is expected to be easier for developers to work with, particularly since the GrainId and StreamId types are now public (they were internal) and GrainId is able to identify any grain.

Version-tolerant, high-performance serialization & RPC

4.x changes how serialization works. The default serializer in previous releases of Orleans is not tolerant to changes in a type's schema. Adding, removing, or renaming a field or type could cause serialization to break without obvious recourse. In 4.x, we introduce a new version-tolerant serializer which allows adding/removing/renaming fields and some type change operations (widening or narrowing a numeric type, for example). We still recommended that developers use JSON serialization for persistence. The new serializer is also designed for high performance and as a result, it is substantially faster than the serializer which it replaces.

Aside from serialization, the remote procedure call internals have been overhauled for performance and flexibility. Previous versions of Orleans use a type called InvokeMethodRequest which identifies the interface and method being invoked using generated integer values and contain an array of objects representing the arguments. The InvokeMethodRequest object is passed to a generated IGrainMethodInvoker implementation which essentially contains some nested switch statements to find the correct method to invoke, then casts the arguments in the object array to the correct types in the invocation. This design has several drawbacks:

  • Handling of overloads and complex generic types is complicated and sometimes intractable
  • It requires boxing primitive argument types, such as int, DateTimeOffset, and user-defined structs
  • Type information must be serialized for all method arguments

Instead, the replacement (which lives in Microsoft.Orleans.Serialization) involves generating an implementation of IInvokable for every method. This interface gives infrastructure components like grain filters enough information to see interface and method information as well as to inspect and manipulate arguments and return values. It is also more efficient to serialize and invoke and relies on the C# compiler to perform method overloading. Generic parameters from generic grain interfaces and generic methods become generic parameters of the implementation, avoiding or offloading most of the complexities involved with generics.

Notice about Breaking Changes

  • Clusters cannot be smoothly upgraded from a previous version of Orleans to Orleans 4.0 via a rolling upgrade.
  • Serializable types must be marked with the [GenerateSerializer] attribute, with any serializable property or field marked with the corresponding [Id(x)] attribute. This is intentionally less magical than in previous versions of Orleans, which did its best to infer what types needed to have serializers generated for them. What this extra ceremony buys you is valuable, though: version tolerance.
  • Since GrainId and StreamId are so different, persistence, streams, and reminders are not forward-compatible yet.
  • We will have more to say regarding our plans to facilitate migration of Orleans 3.x applications to Orleans 4.x.

Here is an example of a type which Orleans would generate a serializer for previously, versus how that same type should be written for Orleans 4.0:

Orleans 3.x and below:

[Serializable]
public class UserProfile
{
    public string DisplayName { get; set; }

    public string PreferredLanguage { get; set; }

    public DateTimeOffset AccountCreated { get; set; }
}

Orleans 4.x and above:

[GenerateSerializer]
public class UserProfile
{
    [Id(0)]
    public string DisplayName { get; set; }

    [Id(1)]
    public string PreferredLanguage { get; set; }

    [Id(2)]
    public DateTimeOffset AccountCreated { get; set; }
}

We have included some analyzers to make this process easier for developers. The first code fix prompts you to add the [GenerateSerializer] attribute for any type which has the [Serializable] attribute:
image
The second analyzer will add [Id(x)] attributes for you:
orleans_analyzer

Full Changelog from 3.0.0 to 4.0.0-preview1: v3.0.0...v4.0.0-preview1

New Contributors

Full Changelog: v3.0.0...v4.0.0-preview1