SpineEventEngine / core-java

The Java implementation of the framework core
https://spine.io
Apache License 2.0
36 stars 12 forks source link

Explicit publishing of Bounded Context API #1301

Open alexander-yevsyukov opened 4 years ago

alexander-yevsyukov commented 4 years ago

Currently we assume that a Bounded Context exposes all the commands and events as its API. Also, we assume that some states of entities (e.g. those of Projections) are always visible too.

Although, most of the commands are expected to be visible to the client API, it may not be the case for all the events. A Bounded Context may expose only some of them thus making them a part of the contract. The rest of the context state may be its own private affairs.

Consider the following piece from the Overselling Event Sourcing blog post by by Alexey Zimarev:

Oskar is completely right about describing state mutations represented by domain events as the essence of the pattern. However, the important part is that domain events live inside the context because they are an integral part of the domain model. Therefore, domain events are rarely exposed to the outside world as-is because it couples the domain model to the service contract since the become the contract. That’s the last thing you want to do. Separating events that go to the outside world, publishing these events as the service contract and keeping them stable is the right thing to do.

It would be good to say that an event is a part of the public API of a context explicitly:

message TaskCreated {
   option (published).to = "all";
   TaskId task = 1 [(required) = true];
}

The code above uses all value to say that the event is published to all the parts of the application. It may be restrictive list which exposes events or entity states to selected contexts, thus making it a bit more secure.

I think we should employ Java Modules feature to control the visibility of the things a context artifacts expose.

A module-info.java for a Bounded Context can then serve as a code implementation of Bounded Context Canvas.

Some links:

armiol commented 4 years ago

Honestly, I don't understand the idea of splitting a single fact in the domain history into multiple events, depending on their "readiness" for the public use.

  1. It may be unnatural to say. E.g. a simple UserRegistered is emitted when a user registered.

I may think of some internal details which may reside inside of this event message, such as an IP address from which the registration has been performed. However, I am not sure why would I split a single event into two just to avoid exposing these.

  1. The problem of migrating the domain API from version to version exists even if we don't speak of events per se. We have the same issue with the read-side models. While Protobuf keeps things backward-compatible, it really makes sense for the additive changes. So if some field is removed either from an event or from the read-side model definition, we have to publish a new API version.

More than that, it has not changed since the pre-event-driven times. We develop a piece of software, we stabilize, we release it under some version. Then, after some cumulative number of changes made, we release another version and tell people how to migrate.

alexander-yevsyukov commented 4 years ago

As discussed vocally, the nature of this request is not about doubling event types here and there. What I want to achieve is analogue of public/private access modifiers done in our meta-language of describing data of Bounded Contexts.

alexander-yevsyukov commented 4 years ago

In addition to the description, I want to be able to specify names of the contexts in the value of the (published).to field so that only specified contexts can receive such a message. E.g.:

message UserCreated {
    option (published).to = "spine.lib.roles";
    //...
}
alexander-yevsyukov commented 4 years ago

@armiol suggested to have a built-in specified for client-side APIs. This way we'd be able to limit the number of exposed types for the programming languages other than Java:

message TaskCreated {
    option (published).to = "_PLATFORM_.dart, _PLATFORM_.js";
}

With _PLATFORM_.all opening access to all client-side languages supported by the framework.

alexander-yevsyukov commented 4 years ago

_PLATFORM_.dart

I only think that the trailing underscore is a bit too much. We can do with the leading only.

alexander-yevsyukov commented 4 years ago

To publish all the types in a file we can use publish_all option, similarly to API status options we already have.

When publish_all is specified in the context.proto file, it opens all the signals and entity state types in the context.

alexander-yevsyukov commented 4 years ago

The publish option is NOT applicable to identifiers and other value objects. We assume that such types are included into the API of a context together with exposed signals or entity states.