diff --git a/docs/client-libs/dart.md b/docs/client-libs/dart.md new file mode 100644 index 0000000..11dd6e8 --- /dev/null +++ b/docs/client-libs/dart.md @@ -0,0 +1,10 @@ +--- +title: Dart Client Library +headline: Documentation +bodyclass: docs +layout: docs +--- +# Dart Client Library + * [Reference documentation]({{site.dart_api_doc}}/client/index.html) + * [Source code]({{site.dart_repo}}/tree/master/client) + diff --git a/docs/client-libs/index.md b/docs/client-libs/index.md new file mode 100644 index 0000000..9a5aa5c --- /dev/null +++ b/docs/client-libs/index.md @@ -0,0 +1,10 @@ +--- +title: Java Client Library +headline: Documentation +bodyclass: docs +layout: docs +--- +# Java Client Library + + * [Reference documentation]({{site.core_api_doc}}/client/index.html) + * [Source code]({{site.core_java_repo}}/tree/master/client) diff --git a/docs/client-libs/js.md b/docs/client-libs/js.md new file mode 100644 index 0000000..9b42fe8 --- /dev/null +++ b/docs/client-libs/js.md @@ -0,0 +1,11 @@ +--- +title: JavaScript Client Library +headline: Documentation +bodyclass: docs +layout: docs +--- +# JavaScript Client Library + + * [Reference documentation]({{site.js_api_doc}}/index.html) + * [Source code]({{site.web_repo}}/tree/master/client-js) + diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..c45ea9f --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,32 @@ +--- +title: Examples +headline: 'Spine Examples' +bodyclass: 'docs' +layout: docs +--- +

Examples are available through the Spine Examples GitHub organization.

+ +

Please see the selected list of the examples with the descriptions below.

+ +## Java + + +## JavaScript + + diff --git a/docs/guides/gradle.md b/docs/guides/gradle.md new file mode 100644 index 0000000..fba3663 --- /dev/null +++ b/docs/guides/gradle.md @@ -0,0 +1,175 @@ +--- +title: Gradle configuration +headline: Documentation +bodyclass: docs +layout: docs +--- + +This guide will lead you through setting up a simple Spine-based project with a Java backend and +a JavaScript frontend. The project defines a domain model using Protobuf, which is then converted +into Java for server usage and JavaScript for client usage. + +# Configuring a project with Gradle + +Spine provides build-time tools for code generation and analysis. These tools are implemented +as [Gradle plugins](https://docs.gradle.org/current/userguide/plugins.html) — an extendable, uniform +way to tap into a project build process. + +The minimal Gradle configuration you will need to start a new project is: + + +```groovy +plugins { + id("io.spine.tools.gradle.bootstrap").version("1.7.0") +} +``` + +Place the config into your root `build.gradle` file and execute a Gradle build. This will apply +the Spine Bootstrap plugin to your project. + +You can also find this declaration on the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/io.spine.tools.gradle.bootstrap), +or on our [Getting Started page]({{site.baseurl}}/docs/quick-start). + +## Spine Bootstrap plugin + +Spine Bootstrap plugin (Bootstrap for short) serves to automate the configuration of the modules +in your Spine-based app. + +We recommend having separate Gradle subprojects for domain model definition, server implementation, +and client code. Bootstrap is applied a bit differently in each of these cases. + +### Model definitions + +Let's assume one of the subprojects contains Protobuf definitions for the domain model. This +subproject is typically called `model`. In `build.gradle` for that subproject, declare: +```groovy +spine.assembleModel() +``` +This way, the subproject will receive all the required Spine dependencies but no code will be +generated from Protobuf. Instead, we generate code for specific languages in subprojects where that +code is needed. By placing model definitions into a separate subproject, we allow them to be shared +between other, language-specific subprojects. + +### Java server implementation + +A separate Gradle subproject is dedicated to the server-side business logic of your Bounded Context. +Usually, this subproject is named `server` or after the Bounded Context it implements, e.g. `users`. +In `build.gradle` for that subproject declare: +```groovy +spine.enableJava().server() + +dependencies { + protobuf(project(':model')) +} +``` +This will add dependencies to the `spine-server` artifact and set up code generation for +the domain model types. Also, use `spine.enableJava().withDatastore()` to add the Google Cloud +Datastore-based storage implementation to the given subproject. + +It is perfectly normal to have more Protobuf types in these modules, as long as those types are +internal to your Java implementation and are not a part of the publicly-visible domain model. + +For any specific subproject, you can configure to run or skip certain code generation routines. +For example: +```groovy +spine.enableJava { + server() // Enable Server API. + codegen { + protobuf = true // Generate default Protobuf Java classes. + spine = false // Avoid enhancing Protobuf Java classes with Spine validation, interfaces, etc. + grpc = true // Generate gRPC stubs and implementation bases from Protobuf service definitions. + } +} +``` + +

+Note the use of the `protobuf` configuration. This tells our tools that the Protobuf definitions +in the subproject `model` must be converted into Java code in the current subproject. + +Alternatively, if, for instance, the upstream project already contains code generated from Protobuf, +and no additional codegen is required, the `api`/`implementation` configurations should be used. See +[this Gradle doc](https://docs.gradle.org/current/userguide/dependency_management_for_java_projects.html) +for more info. +

+ +### Java web server + +If your project contains a JavaScript frontend, you may declare a `web-server` subproject, which +processes the HTTP requests from JS. In `web-server/build.gradle`: +```groovy +spine.enableJava().webServer() + +dependencies { + implementation(project(':server')) +} +``` +Using `webServer()` has the same effect as just declaring the subproject to be a part of `server()` +and also adds the `io.spine:spine-web` dependency to the subproject. This dependency provides +components for handling requests from a JavaScript frontend See also `firebaseWebServer()` for using +a Firebase database to communicate between the server and the client. + +### JavaScript client + +Finally, a JavaScript client is also one or more Gradle subprojects. In `build.gradle` for those +subprojects, declare: +```groovy +spine.enableJavaScript() + +dependencies { + protobuf(project(':model')) +} +``` +This configuration sets up JavaScript code generation from the `model` definitions. Handle NPM +dependencies separately (e.g. adding the dependency for [`spine-web`](https://www.npmjs.com/package/spine-web)). + +### Working with many Bounded Contexts + +In a system with more than one Bounded Context, we recommend a similar project structure. Instead of +having a single `model` subproject, you should form a subproject per Bounded Context, for example, +`users-model`, `trains-model`, `billing-model`, etc. If one of the Bounded Contexts shares some +domain language with another, add a dependency between them. This way, the downstream context may +use Protobuf definitions of the upstream context. +```groovy +dependencies { + implemetation(project(':model-users')) +} +``` + +For domain logic implementation, also use a single subproject per Bounded Context. The convention +for calling those projects by the context names: `users` , `trains`, `billing`, etc. It is a good +idea to have a server implementation subproject depend only on one model subproject to preserve +language and responsibility boundaries. + +If your server should be deployed as a whole, use a single `web-server` for all the contexts. If you +would like to deploy different contexts separately, declare a specific `web-server` subprojects +for each of those contexts. See the [API reference]({{site.core_api_doc}}/server/io/spine/server/ServerEnvironment.html#use-io.spine.server.transport.TransportFactory-io.spine.base.EnvironmentType-) +for the instructions on integrating Bounded Contexts. Also, see [this guide]({{site.baseurl}}/docs/guides/integration.html) +on the principles of integrating separate Bounded Contexts and third-party systems in Spine. + +## Verbose configuration + +If the Bootstrap configuration is not customizable enough for you, there are other Gradle plugins +which may provide fine-grained API. + +Those plugins are Spine Model Compiler for Java subprojects and Spine ProtoJs plugin for JavaScript +submodules. Under the hood, Bootstrap uses those plugins to do the work. This means that Bootstrap +automatically applies the correct low-level plugin for you. + +### Model Compiler + +Spine Model Compiler is a Gradle plugin which executes all the code generation routines via several +Gradle tasks as well as the `modelCompiler { }` extension, which allows you to configure those +tasks. + +See the API reference for the list of the [declared tasks]({{site.base_api_doc}}/plugin-base/io/spine/tools/gradle/ModelCompilerTaskName.html) +and the [codegen configuration options]({{site.base_api_doc}}/model-compiler/io/spine/tools/gradle/compiler/Extension.html) + +### ProtoJS Plugin + +ProtoJs Gradle plugin manages and enhances JavaScript code generation from Protobuf definitions. +The plugin adds the `generateJsonParsers` task, which appends generated JS files with code parsing +Protobuf messages out of plain JS objects. + +The plugin also provides the `protoJs { }` extension, which allows you to configure JS code +generation. See the [API reference]({{site.base_api_doc}}/proto-js-plugin/io/spine/js/gradle/Extension.html) +for more info. diff --git a/docs/guides/index.md b/docs/guides/index.md new file mode 100644 index 0000000..3865f4a --- /dev/null +++ b/docs/guides/index.md @@ -0,0 +1,6 @@ +--- +title: Guides +headline: Documentation +bodyclass: docs +layout: docs +--- diff --git a/docs/guides/integration.md b/docs/guides/integration.md new file mode 100644 index 0000000..762e670 --- /dev/null +++ b/docs/guides/integration.md @@ -0,0 +1,363 @@ +--- +title: Integrating with a third party +headline: Documentation +bodyclass: docs +layout: docs +--- + +# Integration with a third party + +When developing an [Event-based]({{ site.baseurl }}/docs/introduction/concepts.html#event) system, it +is often tricky to integrate it with other software, be it a third party or a legacy system. + +In this article, we will explore the strategies of integrating third-party systems with your +Spine-based [Bounded Context]({{ site.baseurl }}/docs/introduction/concepts.html#bounded-context). + +Note that we think of a third-party system like of yet another Bounded Context with its own +language. The terms “Bounded Context” and “System” are used interchangeably throughout the article. + +To start the integration, your team should decide what portion of the other system’s domain model +you should adopt and how exactly you want to isolate your system from the third party. In DDD terms, +this means choosing the strategy of Context Mapping. + +## Context Mapping — TL;DR + +For the purpose of this article, let’s consider only the technical (and not the organizational) +aspect of a Context Map. You can read more about the concept as a whole in +the ["Domain-Driven Design" book](https://dddcommunity.org/book/evans_2003/) by Eric Evans. + +A Context Map, from a technical perspective, is the relationship between several Bounded Contexts. +The book lists a few archetypes of a Context Map. Some of them are: + + - *Customer/Supplier Models*, which establish an upstream-downstream relation. The Supplier team +tailors its model specifically for the Customer model. For this strategy to work, the developers of +both models have to collaborate. + - The *Conformist* pattern, which is very similar to the Customer/Supplier, except that the +upstream is not available for change. Thus the downstream has to copy the language into its model +thoughtlessly, hence conformism. + - *Anticorruption Layer* pattern (a.k.a. *ACL*), describing a Bounded Context, which is not willing +to accept the language of the upstream and instead builds an intermediate model in the "no man’s +land". The *ACL* translates the language of the upstream into the native language of the downstream +Context. + +

+This list is not exhaustive. The ["Domain-Driven Design" book](https://dddcommunity.org/book/evans_2003/) +offers a few more strategies, all worth considering. However, in this article, we are going to +describe the listed three patterns, because they are the most commonly used. +

+ +## The Domain + +![Big picture domain]({{ site.baseurl }}/img/integrating-with-a-third-party/domain.jpg) + +For the sake of an example, let’s consider airport management software. An airport is a complex +system which relies on many people and much software working together. Let’s consider the system +which helps the flight dispatchers make decisions on **Takeoffs and Landings**. The system +integrates with the software responsible for **Security Checks**, **Airplane Supplies**, and +**Weather**. All of these systems are independent of **Takeoffs and Landings** as well as of each +other. Thus, each of them can be treated as a third party. + +

+**Disclaimer.** The domain of an airport was chosen for being an "easy" example, familiar to many +readers. The system reflects a general impression of an airport and should not be treated as +an accurate representation. +

+ +## Customer/Supplier Contexts + +{: .img-small} +![Customer/Supplier Contexts domain]({{ site.baseurl }}/img/integrating-with-a-third-party/customer-supplier.jpg) + +The **Takeoffs and Landings** system must know whether an *Aircraft* is ready for the *Flight*. +This decision requires data on the supplies, which are provided for the *Aircraft*. To obtain this +knowledge, the system integrates with the **Airplane Supplies** Context. Note the language +difference between the *Aircraft* and an *Airplane*. Apparently, the two Contexts view the same +entity of the real world from different perspectives. +**Airplane Supplies** Context is an integral part of the airport software. Thus, by communicating +with the developers responsible for **Airplane Supplies**, we are able to build a Customer/Supplier +relationship between the **Airplane Supplies** Context and our system. The **Airplane Supplies** +Context does not implement an Event-based messaging internally. However, it still acts as +a Supplier. Specially for **Takeoffs and Landings**, the Supplier Context generates Events and +publishes them to a shared channel. **Takeoffs and Landings**, the Customer, subscribes to those +Events. + +Note that those Events are specifically tailored to be consumed by our system, and thus we do not +have to set up an elaborate Anticorruption Layer. However, a simple adapter is still required to +parse and validate domain Events, which we then publish to a Bounded Context, implemented in Spine. + +![Customer/Supplier Contexts diagram]({{ site.baseurl }}/img/integrating-with-a-third-party/customer-supplier-diagram.svg) + +The Event Consumer, as depicted above, implements the Event transformation logic. In order to +establish this communication channel, the **Airplane Supplies** system declares a [gRPC](https://grpc.io/) +service. In [`supplies_service.proto`](https://github.com/spine-examples/airport/blob/master/airplane-supplies/src/main/proto/spine/example/airport/supplies/supplies_service.proto): + + +```proto +message Subscription { + + string uuid = 1; + EventType event_type = 2; + google.protobuf.Timestamp starting_from = 3; +} +``` + +```proto +enum EventType { + ALL = 0; + + PLANE_FUELED = 1; + ANTI_FROSTING_CHECK_COMPLETE = 2; + PREFLIGHT_CHECK_COMPLETE = 3; +} +``` +The **Airplane Supplies** system [implements](https://github.com/spine-examples/airport/blob/master/airplane-supplies/src/main/java/io/spine/example/airport/supplies/SuppliesEventProducer.java) +the service and exposes it on an endpoint available to the **Takeoffs and Landings** system: + + +```java +public final class SuppliesEventProducer extends SuppliesEventProducerImplBase { +... + @Override + public void subscribe(Subscription request, StreamObserver responseObserver) { +... + Timestamp timestamp = request.getStartingFrom(); + historicalEvents + .stream() + .filter(event -> compare(event.getWhenOccurred(), timestamp) >= 0) + .filter(event -> matches(event, request.getEventType())) + .map(event -> event.toBuilder() + .setSubscription(request) + .build()) + .onClose(responseObserver::onCompleted) + .forEach(responseObserver::onNext); + } +... +} +``` +The event producer obtains cached historical events, matches them to the received subscription, +and sends them to the client. The **Takeoffs and Landings** system implements +an [event consumer](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/supplies/SuppliesEventConsumer.java) +which constructs a subscription and maintains it as long as the system needs to receive more events. +The consumer broadcasts the received Events via an instance of [`ThirdPartyContext`]({{site.core_api_doc}}/server/io/spine/server/integration/ThirdPartyContext.html): + + +```java +@Override +public void onNext(SuppliesEvent event) { + Subscription eventSubscription = event.getSubscription(); + checkArgument(subscription.equals(eventSubscription)); + + _fine().log("Received event `%s`.", eventSubscription.getEventType()); + + ActorContext actorContext = ActorContext + .newBuilder() + .setActor(ACTOR) + .setTimestamp(event.getWhenOccurred()) + .vBuild(); + EventMessage eventMessage = (EventMessage) unpack(event.getPayload()); + context.emittedEvent(eventMessage, actorContext); +} +``` +The [`AircraftAggregate`](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/AircraftAggregate.java) +reacts on those events. Note that all the events published through `ThirdPartyContext` are always +`external`, so should be the subscriber and reactor methods. + + +```java +@React +AircraftPreparedForFlight on(@External PreflightCheckComplete event) { + return AircraftPreparedForFlight + .newBuilder() + .setId(id()) + .vBuild(); +} +``` +## Conformist + +![Conformist domain]({{ site.baseurl }}/img/integrating-with-a-third-party/conformist.jpg) + +**Weather** is an essential aspect of flying a plane, especially at low altitudes. The **Weather** +Context wraps the data received from a meteorological station. This is a true third party to our +system, as our organization, the airport, does not own it. Nearly all the details of a weather +update are important to **Takeoffs and Landings**. The **Weather** Context forces +**Takeoffs and Landings** to conform to its domain model. + +![Conformist diagram]({{ site.baseurl }}/img/integrating-with-a-third-party/conformist-diagram.svg) + +The schema of the conformist relation looks somewhat like the Customer/Supplier schema. Similar to +the Customer/Supplier, **Takeoffs and Landings** Context is downstream from another Context, in this +case from **Weather**. Unlike the Customer/Supplier, **Weather** does not provide a specific +Event Producer, which would adapt **Weather** Events to the needs of **Takeoffs and Landings**. +Also, the Event Consumer on the **Takeoffs and Landings** side is rather thin and devoid of logic. +The Consumer consists of two parts: [`WeatherUpdateClient`](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/weather/WeatherUpdateClient.java) +and [`WeatherUpdateEndpoint`](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/weather/WeatherUpdateEndpoint.java). +The client polls the pull-style API of the **Weather** system. + + +```java +private void fetchWeatherUpdates() { + Instant lastEvent = lastEventTime; + lastEventTime = Instant.now(); + Request getEvents = new Request.Builder() + .get() + .url(weatherService.getSpec() + "/events?since=" + lastEvent.getEpochSecond()) + .build(); + try { + ResponseBody responseBody = client.newCall(getEvents) + .execute() + .body(); + checkNotNull(responseBody); + String responseJson = responseBody.string(); + WeatherMeasurement measurement = WeatherMeasurement.fromJson(responseJson); + endpoint.receiveNew(measurement); + } catch (IOException e) { + logger().atSevere() + .withCause(e) + .log(); + } +} +``` +The endpoint handles the polled measurements and publishes them as Events in +the **Takeoffs and Landings** context: + + +```java +public void receiveNew(WeatherMeasurement measurement) { + checkNotNull(measurement); + if (!previous.isUnknown()) { + TemperatureChanged event = TemperatureChanged + .newBuilder() + .setNewTemperature(measurement.toTemperature()) + .setPreviousTemperature(measurement.toTemperature()) + .vBuild(); + weatherContext.emittedEvent(event, actor); + WindSpeedChanged event1 = WindSpeedChanged + .newBuilder() + .setNewSpeed(measurement.toWindSpeed()) + .setPreviousSpeed(previous.toWindSpeed()) + .vBuild(); + weatherContext.emittedEvent(event1, actor); + } + previous = measurement; +} +``` +The [`FlightAggregate`](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/FlightAggregate.java) +reacts on those events and changes its state as the result: + + +```java +@React +EitherOf2 on(@External TemperatureChanged event) { + float newTemperature = event.getNewTemperature().getDegreesCelsius(); + float previousTemperature = event.getPreviousTemperature().getDegreesCelsius(); + if (abs(previousTemperature - newTemperature) > TEMPERATURE_CHANGE_THRESHOLD) { + return withA(postpone(QUARTER_OF_AN_HOUR)); + } else { + return withB(nothing()); + } +} +``` +## Anticorruption Layer + +![ACL domain]({{ site.baseurl }}/img/integrating-with-a-third-party/acl.jpg) + +**Security Checks** Context has a rich model of its own. The system happens not to use domain Events +at all. The **Security Checks** software, used in our airport, must go through a complex audit and +certification process upon each change. Thus, the cost of changing it is too high. However, +the **Security Checks** also happens to expose an API for fetching the current internal state of +the system. The fetched state has a consistency lag, which never exceeds a known value +(e.g. 2 minutes). In other words, the client of the **Security Checks** API can be sure that +the received data was accurate at most 2 minutes before the query. +Interaction with legacy software with known technical issues can be established with the help of +an Anticorruption Layer. This pattern suggests that we cope with the problems, imposed by the legacy +system, outside our domain model. + +![ACL diagram]({{ site.baseurl }}/img/integrating-with-a-third-party/acl-diagram.svg) + +The Anticorruption Layer (ACL) acts as an interpreter from the language of **Security Checks** +Context into the language of **Takeoffs and Landings** Context. The ACL takes care of polling data +from the **Security Checks** API, filtering it out, transforming it into Events, which can be +understood by **Takeoffs and Landings**, etc. +The idea of an Anticorruption Layer may sound simple. In practice, this is a very powerful tool when +it comes to integrating with a third-party or legacy system. It is often used when splitting up +a large monolithic legacy system (a.k.a. a Big Ball of Mud) into Bounded Contexts. In those cases, +an ACL prevents the new "clean" Contexts from merging back into the Mud. If you are looking for +a way to add functionality to a complex legacy system without increasing the technical debt, look +no further. The Anticorruption Layer between **Takeoffs and Landings** and **Security Checks** +is composed of a [polling client](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/passengers/PassengerClient.java), +which performs all the technical work of obtaining and validating data, and a [Process Manager](https://spine.io/docs/introduction/concepts.html#process-manager) +for the [Boarding process](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/passengers/BoardingProcman.java). +The **Security Checks** API provides data for each passenger independently. The client polls +the data and publishes many intermediate `PassengerBoarded` or `PassengerDeniedBoarding` external +events via [`ThirdPartyContext`]({{site.core_api_doc}}/server/io/spine/server/integration/ThirdPartyContext.html): + + +```java +public void start() { + while (active) { + Request request = requestPassengers(); + try { + List passengers = fetchPassengers(request); + passengers.forEach(this::emitIfStatusKnown); + } catch (IOException e) { + _warn().withCause(e) + .log(); + } + sleepUninterruptibly(HALF_A_MINUTE); + } +} + +private void emitIfStatusKnown(TsaPassenger tsaPassenger) { + BoardingStatus status = tsaPassenger.boardingStatus(); + if (status == BOARDED) { + emitBoarded(tsaPassenger); + } else if (status == WILL_NOT_BE_BOARDED) { + emitDenied(tsaPassenger); + } +} +``` +The [Process Manager](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/passengers/BoardingProcman.java) +accumulates the Events and, once the whole *Flight* is boarded, emits a `BoardingComplete` event, +which is later consumed by the [*Flight* Aggregate](https://github.com/spine-examples/airport/blob/master/takeoffs-and-landings/src/main/java/io/spine/example/airport/tl/FlightAggregate.java). + + +```java +@React +EitherOf2 on(@External PassengerBoarded event) { + PassengerId passenger = event.getId(); + builder().addBoarded(passenger); + return completeOrNothing(); +} + +@React +EitherOf2 on(@External PassengerDeniedBoarding event) { + PassengerId passenger = event.getId(); + builder().addWillNotBeBoarded(passenger); + return completeOrNothing(); +} +``` +## In Conclusion + +An integration job may seem complicated or even overwhelming. However, with a strong understanding +of the domain and good tooling, the process boils down to a few simple steps. Indeed, it does not +matter, whether you want to integrate with an outside system, your own legacy system, or reorganize +your current system to work in an event-driven manner. A correct integration strategy will help you +isolate and perfect your own domain language while on the work of many external systems. Read more +about Bounded Contexts and their interactions in the "Domain-Driven Design" book by Eric Evans. + +The full version of the source code used in this article could be found in the [Airport Example repository](https://github.com/spine-examples/airport). diff --git a/docs/guides/rejections.md b/docs/guides/rejections.md new file mode 100644 index 0000000..ad0355d --- /dev/null +++ b/docs/guides/rejections.md @@ -0,0 +1,28 @@ +--- +title: Working with Rejections +headline: Documentation +bodyclass: docs +layout: docs +--- +# Working with Rejections + +Compared with regular Events, [Rejections](/docs/introduction/concepts.html#rejection) are defined +differently. Here is the summary of the differences: + +1. `java_multiple_files` file option must be set to `false` + + By doing this we instruct Protobuf Compiler to put all the rejection classes in a single + outer class. + Spine Model Compiler for Java generates `ThrowableMessage` classes for all these messages. + These classes will be named after the classes of rejection messages. + Putting rejection message classes under an outer class avoids name clash inside the package. + +2. Omit `java_outer_classname` option + + Thus, the outer class name is derived from the name of the file where rejection messages are + declared. Usually the outer class names are named using the name with the suffix `Proto`. + We want the name to end with `Rejections` so that it is clearly visible what is inside + this class. + + + diff --git a/docs/guides/start-new-project.md b/docs/guides/start-new-project.md new file mode 100644 index 0000000..f925abc --- /dev/null +++ b/docs/guides/start-new-project.md @@ -0,0 +1,260 @@ +--- +title: Starting a new project +headline: Documentation +bodyclass: docs +layout: docs +--- + +# Starting a new project + +

Starting off a new project goes smoother when you have an efficient +and straightforward development flow to rely on. In this guide, we describe how we usually start +and develop new projects.

+ +While the key stages of the development process are described in the [Introduction][introduction] +section, this guide provides more hands-on details on the steps that we follow while working +on a project. + +## TL;DR + +In short, please follow the next steps to have a consistent and joyful development flow. + +

Each step and sub-step below results in a separate Pull Request +adding its artifacts to the repository.

+ +1. Conduct [EventStorming][EventStorming] to gather domain knowledge. +Digitize the Artifact and store it in the code repository. + +2. Pick up a [Bounded Context][BoundedContext-concept]. + +3. Define [identifiers][identifier-concept] for the entities of the selected context. + +4. Define [signals][signals]: + + 4.1 Define [events][event-concept]. + + 4.2 Define [commands][command-concept]. + + 4.3 Define [rejections][rejection-concept]. + +5. Pick up a [scenario](#picking-up-a-scenario) (a use case, a process, or a flow) +within the Bounded Context: + + 5.1. Define [entity][entity-concept] states for the scenario. + + 5.2 Implement the scenario server-side functionality in Java code. + Cover the business logic with [`BlackBox`][testing] integration tests. + + 5.3 Fulfill the scenario vertically: create UI, public API, or a client whichever is required. + +6. Repeat step 5 until all the scenarios are covered. + +7. Repeat steps 2 through 6 for the other contexts. + +The sections below describe the development process in more detail. + + +## Getting started with a domain: EventStorming + +The first thing to get the project done is to conduct an [EventStorming][EventStorming] session +with the Domain Experts in the chosen field. + +The EventStorming allows both the business people and the engineers to start talking using +the same “language” fast. It is important for engineers to avoid using technical jargon. + +The results of the EventStorming (all the stickies) are captured as the Artifact and stored +as a part of the project documentation. + +

We store the EventStorming Artifact electronically as images under the project root +in the `/docs/eventstorming/` folder. If the session is performed offline, the photos +of the EventStorming board are stored in the repository. In case of an online session, +the screenshots of the board are stored.

+ +![An example of the EventStorming board]({{ site.baseurl }}/img/starting-a-new-project/eventstorming-board.jpg) +

An example of the EventStorming board

+ +After the session, a dedicated person creates a Pull Request with the Artifact, and the team reviews +it once again. This first EventStorming is usually addressed as a "Big Picture" and gives +the team and the experts a broad overview of the problem they are trying to solve. + +Going forward, the next EventStorming "Process Modeling" and "Software Design" sessions +produce updates to the Artifact. + +Depending on the size and the scope of the project, you may need to conduct multiple EventStorming +sessions with different experts. + +## Limiting the scope: pick up a Bounded Context + +While the temptation to dive into the development of everything right away may be humongous, +we recommend limiting the development scope down to only one +[Bounded Context][BoundedContext-concept]. + +

We follow the rule: "Eat an elephant one bite at a time".

+ +You may need another EventStorming session to go into more detail. + +![An example of a Bounded Context]({{ site.baseurl }}/img/starting-a-new-project/bounded-context.jpg) +

An example of a Bounded Context

+ +## Shaping the language + +With the selected Bounded Context in mind, we continue with the creation of the first code +artifacts of the project. During this step we define Protobuf messages that mold the +[Ubiquitous Language][UbiquitousLanguage] of the context. + +The results of these efforts are the `.proto` files [grouped][project-structure] under a specific +package in the `proto` folder. + +

If you are new to Protobuf, please see the [Naming Conventions][naming-conventions] +section for how to name things in the proto code.

+ +While writing the protos, make sure to document **all** messages. It's time to unleash +your technical writing skills and lay the project's ground-standing foundation. +Here you may want to introduce some domain-level validation logic. Check out the +[Validation guide][validation-guide] for details. + +### Identifiers + +We put this step aside because in [Reactive DDD][ReactiveDDD] entities reference each other using +the typed [identifiers][identifier-concept]. + +

Consider following the [Vaughn Vernon][VaughnVernon]’s rule on Aggregates from +the “Effective Aggregate Design Part II” that is applicable to **any** entity: +“Reference other Aggregates by Identity”

+ +We recommend using message-based identifiers over simple types to make the API strongly pronounced +and type-safe. To make things obvious, consider putting the IDs of the context into the file named +[`identifiers.proto`][identifiers-proto]. This file will be imported when defining events, +commands, entity states, and other types of the selected context. + +

Please consult with the Naming Conventions [guide][identifiers-naming] for our +recommendations on naming the identifier types.

+ +When the ID types are defined, please create a Pull Request so that the team can review +and polish the code of this important development step. + +### Events + +When the IDs are defined it’s time to define [event][event-concept] messages. The events are named +as facts formulated as past participles, e.g. `RepositoryRegistered` or `TaskCreated`. +They are defined in files with the [`_events.proto`][events-proto] suffix (e.g. `order_events.proto`, +`customer_events.proto`). If your context is small it can be just `events.proto`. + +Create a Pull Request with the event definitions when they are ready. + +### Commands + +Similar to events, [command][command-concept] messages are defined in files having the names ending +with the [`_commands.proto`][commands-proto] suffix (or just `commands.proto` for a small context). +Commands are defined as imperative in a form of “do something”, e.g. `RegisterRepository` +or `CreateTask`. + +Finalize defining commands with a Pull Request. + +### Rejections + +[Rejections][rejection-concept] are special events that denote a command failed for a reason. +The rejection messages are defined in files with the [`_rejections.proto`][rejections-proto] suffix +(or just `rejections.proto`). For more information on the rejections, please refer to the +[“Working with Rejections”][rejections-guide] guide. + +Create a new Pull Request and review rejection definitions. + +## Picking up a scenario + +A scenario is a defined finite part of the context. It can be either a use case, a business process, +or a complete functional flow. + + + +If you see it getting too big, it may be worth splitting it into two or more smaller parts. +For example, you may want to start with a single [Aggregate][aggregate-concept] or a +[Process Manager][process-manager-concept]. + +### Entity states + +The [entity state][entity-state-naming] is a holder of the entity data. It does not represent +a whole [entity][entity-concept] but depicts the shape of its data. + +The definitions of entity states are [gathered][entity-state-proto] in a file named after +a business model thing. E.g. for a `Task` aggregate, the definitions would be defined in +a `task.proto` file. + +As with the other steps, create a Pull Request to review the entity states with the team. + +### Adding behavior + +We recommend implementing the scenario with the Java implementation of the domain +[entities][entities] and the [`BlackBox`][testing] integration tests. The `BlackBox` tests are +the recommended way to test scenarios in Spine. They are specifically built to allow you to check +the business logic the same way it works in the application. + + + +All the code must conform to your standards of the code and documentation quality +and be tested thoroughly. + +When a backend for the scenario is done a new PR is created and reviewed. + +### Fulfilling the vertical + +We usually do the vertical development, meaning an engineer starts with the domain definition, +continues with its implementation, and finishes with the front-facing tasks. + +Depending on your team workflow and preferences this step can take place in parallel +with the previous one. + +As noted, the scope of this iteration is to prepare the front-facing part for the scenario: +either a UI if one is needed, or the public API, or a dedicated idiomatic client. + +As soon as the implementation is ready, another PR and review come along. + +## Start over again + +When you have finished with the scenario, pick up a new one and start it over again following the PR +and review process. + +As soon as you are done with the Bounded Context, move on to the next one. + +## Summary + +While developing a project, make sure to split the development by Bounded Contexts. Pick up +a context and split it into scenarios. Make sure each of the development steps results +in a separate Pull Request with dedicated artifacts in the source code repository. +Opt for smaller, fine-graded Pull Requests instead of cluttered and complicated ones. + +[introduction]: {{ site.baseurl }}/docs/introduction "Check the Introduction" +[project-structure]: {{ site.baseurl }}/docs/introduction/project-structure.html#example "Check out the Example Project structure" +[naming-conventions]: {{ site.baseurl }}/docs/introduction/naming-conventions.html "Check out the Naming Conventions" +[validation-guide]: {{ site.baseurl }}/docs/guides/validation.html "Learn more about the Validation" +[BoundedContext-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#bounded-context "Check out the Bounded Context definition" +[aggregate-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#aggregate "Check out the Aggregate definition" +[process-manager-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#process-manager "Check out the Process Manager definition" +[signals]: {{ site.baseurl }}/docs/introduction/#getting-domain-knowledge "Learn more about Signals" +[testing]: {{ site.baseurl }}/docs/introduction/#testing "Learn more about Testing" + +[identifier-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#identifier "Learn more about Identifiers" +[identifiers-proto]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#identifiersproto "Learn more about Identifiers proto structure" +[identifiers-naming]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#identifiers "Learn more about Identifiers naming conventions" + +[event-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#event "Learn more about Events" +[events-proto]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#eventsproto "Learn more about Events proto structure" + +[command-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#command "Learn more about Commands" +[commands-proto]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#commandsproto "Learn more about Commands proto structure" + +[rejection-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#rejection "Learn more about Rejections" +[rejections-proto]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#rejectionsproto "Learn more about Rejections proto structure" +[rejections-guide]: {{ site.baseurl }}/docs/guides/rejections.html "Learn more about Rejections" + +[entities]: {{ site.baseurl }}/docs/introduction/#entities "See more examples on entities" +[entity-concept]: {{ site.baseurl }}/docs/introduction/concepts.html#entities "Learn more about Entities" +[entity-state-naming]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#entity-states-1 "Learn more about Entity states" +[entity-state-proto]: {{ site.baseurl }}/docs/introduction/naming-conventions.html#entity-states "Learn more about Entity states proto structure" + +[EventStorming]: https://eventstorming.com "Learn more about EventStorming" +[UbiquitousLanguage]: https://martinfowler.com/bliki/UbiquitousLanguage.html "Learn more about the Ubiquitous Language" +[ReactiveDDD]: https://www.infoq.com/presentations/reactive-ddd/ "Check out the Reactive DDD presentation" +[VaughnVernon]: https://vaughnvernon.co/ diff --git a/docs/guides/validation.md b/docs/guides/validation.md new file mode 100644 index 0000000..4fa5590 --- /dev/null +++ b/docs/guides/validation.md @@ -0,0 +1,647 @@ +--- +title: Validation User Guide +headline: Documentation +bodyclass: docs +layout: docs +--- +# Validation User Guide + +

Building a good domain model requires more than just defining data structures. +One of the commodities required for describing domain specifics is making sure that the data +is correct. This guide will walk you through the API the Validation Library which helps achieving +this goal.

+ +All of the validation features described here are currently supported in the Java environment. +Many are supported in Dart as well. For more info, see the description of individual features +given in the sections below. + +## Overview +Spine uses Protobuf for defining data structures of the domain models. The constraints +that define correctness of data are also defined at this level using custom Protobuf options +[offered](#validation-options) by the Validation Library. + +

In order to use validation features, you don't need to understand how custom +options work. Those who are interested in the details of this advanced feature of Protobuf, +please see the [Protobuf Guide](https://developers.google.com/protocol-buffers/docs/proto3#custom_options) +for details.

+ +Here are simple steps in adding validation to the data model: + 1. The programmer adds validation constraints to the Protobuf types of the model. + 2. Spine Model Compiler generates the code which provides validation features. + 3. The programmer calls the validation API of these data types as their instances are created. + +## Java validation API + +For Java, we generate additional code to the Protobuf message classes. In particular, the message +builders get one extra method: `vBuild()` — short for "validate and build". It acts just like +`build()` but also throws a `ValidationException` if the message is not valid: + +```java +MyMessage.newBuilder() + .setFoo(invalidValue()) + .vBuild(); // ← Throws ValidationException. +``` + +If the validation is not required, you may call `build()` or `buildPartial()` provided by Protobuf +Java API. + +The message class also gets an extra method — `validate()`. This method does not throw exceptions. +Instead, it returns a list of `ConstraintViolation`s: + +```java +MyMessage msg = MyMessage.newBuilder() + .setFoo(invalidValue()) + .buildPartial(); +List violations = msg.validate(); +``` + +If the message is valid, the list is empty. If one or more constraints are violated, all +the violations will be present in the list. + +## Dart validation API + +In Dart, we generate functions for validating messages separately from the message classes. Those +functions can be accessed via the known types: + +```dart +var msg = getMessage(); +var validate = theKnownTypes.validatorFor(msg); +ValidationError error = validate(msg); +``` + +Similarly to `validate()` method in Java, the validation function does not throw exceptions. A list +of `ConstraintViolation`s can be obtained from the `ValidationError`. + +## Validation options overview + + +In most cases validation constraints are defined for Protobuf message fields such as if a field +must be populated or it must be withing a range, or match a regular expression. Not so often +it may be necessary to require a combination of fields. In this case, validation options are defined +at the level of a corresponding message type. + +## Required fields + +When modelling a domain, we often come up to certain data points which cannot be skipped. Those are +represented by required fields of an entity state, a Command, an Event, etc. + +

+Protobuf 2 used to have a native support for required fields. +However, from the serialization perspective, that proved to be +a [design mistake](https://stackoverflow.com/a/31814967/3183076). If a required field was missing, +the message could not be serialized and sent over the wire. Also, it is often too easy to add a new +required field, thereby breaking backwards-compatibility of the message type. In Protobuf 3 all +the fields are optional.

+ +In the Validation Library, we've revived the concept of required fields, but on a different level. +The difference to the Protobuf 2 way is that out required fields do not affect the serialization +of the message. +If a required field is missing, it still can be serialized and passed over the wire. By separating +validation from serialization, we allow users to choose to ignore validation errors and still +transfer messages over the wire when needed. + +### How required fields work + +Fields in Protobuf may have either a primitive type or a user-defined type. A user-defined type is +a `message` or an `enum` and primitive types are numbers, `string`, and `bytes`. If a `message` or +an `enum` field is not set, the default value is assigned automatically: + +```java +ZonedDateTime time = getZonedTime(); +ZoneId timeZone = time.getZoneId(); +assert timeZone.equals(ZoneId.getDefaultInstance()); +``` + +However, due to limitations of the binary format, there is no way to tell if a numeric field is set +to `0` or just not set: + +```java +LocalTime time = getTime(); +int hours = time.getHours(); +assert hours == 0; +``` + +This means that a numeric field cannot be required, as there is no way to check if it is set. All +the other fields can be required. For `message` fields this means that the message must not be +empty: + +```java +ZonedDateTime time = getZonedTime(); +LocalDateTime dateTime = time.getDateTime(); +assert !dateTime.equals(LocalDateTime.getDefaultInstance()); +``` + +For `enum` fields, this means that the enum value must have a number other than `0` (since +the enum value with number `0` is the default value of the field): + +```java +LocalDate date = getDate(); +Month month = date.getMonth(); +assert !month.equals(Month.MONTH_UNDEFINED); +``` + +For `string` and `bytes` fields this means that the sequence must not be empty: + +```java +PersonName name = getName(); +String givenName = name.getGivenName(); +assert !givenName.isEmpty(); +``` + +For collection fields (i.e. `repeated` and `map`), a field is considered set if: + 1. The collection is not empty. + 2. At least one of the entries (values for `map`s) matches the rules described above. + +Note that collections of numeric fields can be required. In those cases, only the rule 1. applies +and the rule 2. is ignored. + +### Declaring required fields + +In the basic scenario, a single required field is marked with the `(required)` option: + +```proto +import "spine/options.proto"; + +// A phone number represented by a string of digits. +message PhoneNumber { + string digits = 1 [(required) = true]; +} +``` + +Here, the field `PhoneNumber.digits` is required. If the API user tries to validate an instance of +`PhoneNumber` without this field, a `ConstraintViolation` is produced: + +```java +PhoneNumber.newBuilder() + .setDigits("") + .vBuild(); // ← Throws ValidationException. +``` + +There are more complex cases for required fields than just a single field. Consider a `oneof` field +group, which always has to be set. Applying `(required)` to the fields does not make sense, since +only one field in the group can be set at a time. Instead, Spine provides `(is_required)` option: + +```proto +import "spine/options.proto"; +import "spine/net/email_address.proto"; +import "acme/auth.proto"; + +// The means to identify a user. +message UserIdentity { + oneof auth_type { + option (is_required) = true; + + spine.net.EmailAddress email = 1; + auth.GoogleId google = 2; + auth.TwitterId twitter = 3; + } +} +``` + +In this case one of the fields `UserIdentity.email`, `UserIdentity.google`, +and `UserIdentity.twitter` must be set. + +

+`(is_required)` option is not yet supported in Dart. +

+ +In some other cases, a field may be either required or not, depending on the value of another field. +Consider an example of an online store item: + +```proto +import "spine/options.proto"; +import "spine/core/user_id.proto"; +import "google/protobuf/timestamp.proto"; + +// A product which can be purchased at the online store. +message Item { + // ... + + google.protobuf.Timestamp when_opened_for_sale = 42; + spine.core.UserId who_opened_for_sale = 43 [(goes).with = "when_opened_for_sale"]; +} +``` + +The `Item.who_opened_for_sale` field only makes sense for the domain if +the `Item.when_opened_for_sale` field is set. If `who_opened_for_sale` is set and +`when_opened_for_sale` is not, a constraint violation is produced. + +Finally, there are some cases, in which a pair of fields may be set at the same time, but at least +one of them must be set. This and more complex cases are handled by the type-level +`(required_field)` option: + +```proto +import "spine/options.proto"; + +// A name of a person. +message PersonName { + option (required_field) = "given_name|honorific_prefix & family_name"; + + string honorific_prefix = 1; + string given_name = 2; + string middle_name = 3; + string family_name = 4; + string honorific_suffix = 5; +} +``` + +In case of `PersonName`, either `given_name` or both `honorific_prefix` and `family_name` must be +set. All three can be set at the same time. + +### Missing fields + +In case if a required field is missing, the validation error message will explicitly say so. +However, if you need a specific error message for this field, you can provide it via +the `(if_missing)` option: + +```proto +import "spine/options.proto"; + +// A phone number represented by a string of digits. +message PhoneNumber { + string digits = 1 [(required) = true, + (if_missing).msg_format = "Phone number must contain digits."]; +} +``` + +Note that this option only applies to fields marked with `(required)` and not to the fields +referenced via any other options. + +If `(goes)` option is used, the error message can be customized with the `(goes).msg_format` +parameter. Note that the message should contain two "`%s`" insertion points: first for the name of +the field declaring the option and second for the name of the field targeted by the option. + +### When `(required)` is implicit + +When defining the domain [Commands]({{site.baseurl}}/docs/introduction/naming-conventions.html#commandsproto), +[Events]({{site.baseurl}}/docs/introduction/naming-conventions.html#eventsproto), or entity states, we have found +to be convenient that the first field of the respective Message is the identifier. Therefore, by +convention, Spine treats the first fields of such objects as their IDs: + +```proto +import "spine/options.proto"; +import "spine/core/user_id.proto"; +import "spine/people/person_name.proto"; + +// The state of the User Aggregate. +message User { + option (entity).kind = AGGREGATE; + + spine.core.UserId id = 2; + spine.people.PersonName name = 1; + // ... +} +``` + +In this case, the `User.id` field is implicitly `(required) = true`. Note that the field __number__ +has nothing to do with this convention, only the field __order__. Thus, `User.name` is not required. + +For the next example, consider `user_events.proto`: + +```proto +import "spine/options.proto"; +import "spine/net/url.proto"; +import "spine/core/user_id.proto"; + +// An event emitted when a user's profile picture is changed. +message ProfilePictureChanged { + + spine.net.Url new_picture = 1 [(required) = false]; + spine.core.UserId user = 2; +} +``` + +In this case, the `ProfilePictureChanged.id` field is not required, since it's not declared first +in the field. The field `ProfilePictureChanged.new_picture` is not required because the convention +is overridden with an explicit option. + +## Nested message validation + +When a message is validated, only the "shallow" constraints are checked by default. This means that +the message fields can be invalid and the container message is still considered valid. + +In order to enable message field checks, use `(validate)` option: + +```proto +import "spine/options.proto"; +import "spine/people/person_name.proto"; + +// The state of the User Aggregate. +message User { + // ... + + spine.people.PersonName name = 2 [(validate) = true]; +} +``` + +When an instance of `User` is validated, constraints of `User.name` will also be checked. +If any violations are found, they will be packed into a single violation of the `User` message. + +```java +// Honorific prefix not set and `name` is not valid. +PersonName name = PersonName + .newBuilder() + .setFamilyName("Smith") + .build(); // Build without validation. +User user = User + .newBuilder() + .setPersonName(name) + .vBuild(); // ← Throws ValidationException. +``` + +When applied to a `repeated` or a `map` field, each item (value of a `map`) is validated. + +

+`(validate)` option is not yet supported in Dart. +

+ +#### Invalid fields + +If a specific error message is required for an invalid field, the `(if_invalid)` option should be +used: + +```proto +import "spine/options.proto"; +import "spine/people/person_name.proto"; + +// The state of the User Aggregate. +message User { + // ... + + spine.people.PersonName name = 2 [(validate) = true, + (if_invalid).msg_format = "User name is invalid."]; +} +``` + +## Number bounds + +For numeric fields, Spine defines a few options to limit the range of expected values. + +### `(min)`/`(max)` + +`(min)` and `(max)` are twin options which define the lower and higher bounds for a numeric fields. +The value is specified as a string. Note that the string must be parsable into the field's number +format (e.g. a `int32` field cannot have a `"2.5"` bound). + +By default, the bounds are __inclusive__. Use the `exclusive` property to make a bound exclusive. + +Example: + +```proto +import "spine/options.proto"; + +// A distance between two points of a map with a millimeter precision. +message Distance { + + uint64 meters = 1; + uint32 millimeters = 2 [(max) = { value: "1000" exclusive: true }]; +} +``` + +### Ranges + +The `(range)` option is a shortcut for a combination of `(min)` and `(max)`. A range specifies both +boundaries for a numeric field. `(range)` is a `string` option. The `(range)` notation allow +declaring inclusive and exclusive boundaries. A round bracket ("`(`" or "`)`") denotes an exclusive +boundary and a square bracket ("`[`" or "`]`") denotes an inclusive one. + +Example: + +```proto +import "spine/options.proto"; + +// A time without a time-zone. +// +// It is a description of a time, not an instant on a time-line. +// +message LocalTime { + + int32 hours = 1 [(range) = "[0..23]"]; + int32 minutes = 2 [(range) = "[0 .. 60)"]; + float seconds = 3 [(range) = "[0 .. 60.0)"]; +} +``` + +In the example above, the `LocalTime.hours` field can span between 0 and 23, the `LocalTime.minutes` +field can span between 0 and 59, and the `LocalTime.seconds` field can span between 0.0 and 60.0, +but can never reach 60. Exclusive boundaries are especially powerful for fractional numbers, since, +mathematically, there is no hard upper limit which a field value can reach. + +Usage of the double dot separator ("`..`") between the bounds is mandatory. + +

+In some languages, Protobuf unsigned integers are represented by signed language primitives. +For example, in Java, a `uint64` is represented with a `long`. If a value of a field in Java will +overflow into `long` negatives, it will be considered a negative by the validation library. Keep +that in mind when defining lower bounds. +

+ +## Regular expressions + +For `string` fields, the library provides the `(pattern)` option. Users can define a regular +expression to match the field values. Also, some common pattern modifiers are available: + - `dot_all` (a.k.a. "single line") — enables the dot (`.`) symbol to match all the characters, + including line breaks; + - `case_insensitive` — allows to ignore the case of the matched symbols; + - `multiline` — enables the `^` (caret) and `$` (dollar) signs to match a start and an end of + a line instead of a start and an end of the whole expression; + - `unicode` — enables matching the whole UTF-8 sequences; + - `partial_match` — allows the matched strings to contain a full match to the pattern and some + other characters as well. By default, a string only matches a pattern if it is a full match, + i.e. there are no unaccounted for leading and/or trailing characters. + +Example: + +```proto +import "spine/options.proto"; + +// A link to an HTTP(S) resource. +message HyperReference { + string url = 1 [(pattern) = { + regex: "https?://.+\\..+" + modifier: { + case_insensitive: true + } + }]; +} +``` + +It is recommended to use simple patterns due to performance considerations. For example, fully +fledged URL and email patterns are famously too long to be used in most cases. Treat `(pattern)` +checks as if they were yet another code with regex matching in it. + +## Temporal constraints + +Spine provides an option for validating time-bearing types. Those are: + - `google.protobuf.Timestamp`; + - `spine.time.YearMonth`; + - `spine.time.LocalDate`; + - `spine.time.LocalDateTime`; + - `spine.time.OffsetDateTime`; + - `spine.time.ZonedDateTime`; + - any user-defined type which implements the Temporal interface (`io.spine.time.Temporal` for + Java). + +Using the option `(when)`, you may declare that the timestamp should lie in past or in future. + +```proto +import "spine/time_options.proto"; +import "spine/time/time.proto"; + +// A command to place an order. +message PlaceOrder { + + // ... + + spine.time.ZonedDateTime when_placed = 12 [(when).in = PAST]; + spine.time.ZonedDateTime when_expires = 13 [(when).in = FUTURE]; +} +``` + +Note that the value is checked in relation to the current server time. In most cases, this should +not be an issue. However, be aware that using `FUTURE` in Events and entity states may cause +validation errors when the future comes. Since entity states are validated upon each state change, +and historical events can be replayed, avoid declaring parts of those domain objects to be in +future. Commands, on the other hand, are not replayed or stored automatically. Thus, It is safe +to use `FUTURE` in Commands. + +## Distinct collections + +Often, a `repeated` field logically represents a set rather than a list. Protobuf does not have +a native support for sets. Moreover, it is often an invalid operation to add a duplicate element to +a set. For such cases, Spine provides the `(distinct)` option, which constrains a `repeated` or +a `map` field to only contain non-duplicating elements (values in case of `map`s). + +Example: + +```proto +import "spine/options.proto"; +import "spine/net/email_address.proto"; + +// The state of the User Aggregate. +message User { + // ... + + repeated spine.net.EmailAddress recovery_email = 42 [(distinct) = true]; +} +``` + +## Non-mutable fields + +Some messages persist in your system through a stretch of time. The value represented by such +a message may change. However, some fields must not change ever. For checking that, Spine allows +marking fields as `(set_once)`. The option allows changing a value of a field only if the current +value is the default value. Changing a field from a non-default value to anything else will cause +a violation. + +In Java, you can validate messages against a `set_once` constraint via +the `Validate.checkValidChange()` method. For example: + +```java +MyMessage old = getMessage(); +MyMessage changed = doSomeStuff(old); +Validate.checkValidChange(old, changed); +``` + +`Validate.checkValidChange()` throws a `ValidationException` if the constraint is violated. + +

+In Dart, there is no support for this feature. +

+ +Many fields of an entity are immutable. They may be set once in the life of the entity and then +should never be changed. The `(set_once)` constraint is checked automatically for entity states upon +each change. + +Example: + +```proto +import "spine/options.proto"; +import "google/protobuf/timestamp.proto"; + +// The state of the Order Aggregate. +message Order { + option (entity).kind = AGGREGATE; + + // ... + + google.protobuf.Timestamp when_deleted = 314 [(set_once) = true]; +} +``` + +Once the `Order.when_deleted` field is filled, it can never change. + +## External constraints + +Sometimes, you need to impose extra validation rules on types you do not control. Consider +the example of an image URL which should always have the `ftp` protocol. In Spine, a `Url` is a tiny +type for representing URL strings: + +```proto +package spine.net; + +// ... + +// A Universal Resource Locator. +// +message Url { + + // The value of the URL. + string spec = 3 [(required) = true]; + + reserved 1, 2; +} +``` + +Now, we will use this type in our domain definition: + +```proto +import "spine/net/url.proto"; + +// The state of the User Aggregate. +message User { + // ... + + spine.net.Url profile_picture = 42; +} +``` + +How do we add validation to the `Url` so that only the `User.profile_picture` is +affected? Just for this purpose, Spine provides the mechanism of external constraints — validation +constraints defined outside the message. + +To declare an external constraint, use the `(constraint_for)` option: + +```proto +import "spine/options.proto"; + +// The external constraint definition for `User.profile_picture`. +message UserPictureConstraint { + option (constraint_for) = "org.example.user.User.profile_picture"; + + string spec = 3 [ + (required) = true, + (pattern).regex = "ftp://.+", + (pattern).msg_format = "Profile picture should be available via FTP (regex: %s)." + ]; +} +``` + +The definition of `User` itself need not change. + +Note that the fields of an external constraint declaration should replicate the fields of the target +type. In our example, the `Url` type. If the `Url` type had many fields, only those which need any +validation should be declared. However, note that if the `Url` type declares any validation on its +own, all of it is discarded and only the "substitute" rules from the `UserPictureConstraint` are +used. + +

+External constraints are not yet supported in Dart. +

+ +

+Mind performance considerations when declaring external constraints. It is expected that the number +of such constrains in the whole project is not large, significantly smaller than the number of +normal constraints. This mechanism is not designed to override validation rules of an entire library +of Protobuf definitions, merely a small amount of local patches. +

diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0b60d4e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,35 @@ +--- +title: Documentation +headline: Documentation +bodyclass: docs +layout: docs +next_btn: false +--- +# Welcome +

Welcome to the Spine developer documentation. This page gives the overview of +the documentation sections.

+ +## [Quick Start]({{ site.baseurl }}/docs/quick-start/) +In this section you can learn what it's like to develop with Spine by going through the code of +the [Hello World](https://github.com/spine-examples/hello) example. + +## [Introduction]({{ site.baseurl }}/docs/introduction/) +This section gives an overview of the development process, the architecture of the Spine-based +application, information on DDD concepts implemented by the framework and how framework +users deal with them. + +## [Guides]({{ site.baseurl }}/docs/guides/validation) +This section provides detailed instructions on the framework use. + +## [Client Libraries]({{ site.baseurl }}/docs/client-libs/) +This section provides language-specific guides for building client-side applications. + +## [API Reference]({{ site.baseurl }}/docs/reference/) +This sections provides links to the generated documentation. + +## [Examples]({{ site.baseurl }}/docs/examples/) +This page is the entry point for learning from the code of +the [example applications](https://github.com/spine-examples/). + +## [DDD Resources]({{ site.baseurl }}/docs/resources/) +A brief selection of learning materials we recommend to the colleagues in DDD. diff --git a/docs/introduction/architecture.md b/docs/introduction/architecture.md new file mode 100644 index 0000000..0b39c9a --- /dev/null +++ b/docs/introduction/architecture.md @@ -0,0 +1,32 @@ +--- +title: Architecture +headline: Documentation +bodyclass: docs +layout: docs +customjs: /js/architecture-diagram.js +--- +# Application Architecture + +
+A Spine-based application consists of several Bounded Contexts. Client applications interact +with the server-side via `CommandService`, `QueryService`, and `SubscriptionService`. + +The diagram below shows all server-side components +of a cloud application. When developing with Spine, you will be interacting +with only some of them, which +are not shaded on the diagram. The rest is handled by the framework. + +

Click on a component to navigate to its definition from +the [Concepts]({{site.baseurl}}/docs/introduction/concepts.html) page.

+ +
+{% include_relative diagrams/spine-architecture-diagram.svg %} + + +
+ diff --git a/docs/introduction/concepts.md b/docs/introduction/concepts.md new file mode 100644 index 0000000..f21061b --- /dev/null +++ b/docs/introduction/concepts.md @@ -0,0 +1,284 @@ +--- +title: Concepts +headline: Documentation +bodyclass: docs +layout: docs +--- +# Concepts + +

This document provides terminology used in the framework and its documentation. +You'll find most of the terms familiar from Domain-Driven Design, CQRS, Event Sourcing or Enterprise +Integration patterns. +

+We give brief descriptions for those who are new to these concepts and to tell +how they are implemented in our framework. +Terms extending the industry-set terminology are designated as such.

+ +## Messaging + +### Command + +Commands are messages that instruct an entity within Spine framework to perform a certain action. +Compared with events, a command is not a statement of fact. They are a request, and, thus, can be +refused. A typical way to convey refusal is to throw an error or rejection. + +In Spine, commands are defined as Protocol Buffer (a.k.a Protobuf) messages in the file which name +ends with `commands.proto`. + +### Event + +Event is something that happened in the past. All changes to an application state are captured as +a sequence of events. Events are the main “database” of the application. + +In Spine, events are defined as Protobuf messages in the file which name ends with `events.proto`. + +### Rejection + +Rejections is a special “negative” kind of events that we introduce to differentiate them from +regular events. If an event is a fact of something that happened to a domain model, a rejection is +a fact that states the reason why a command was not handled. + +Consider the following examples of rejections: +* `CreditCardValidationDeclined`, +* `OrderCannotBeEmpty`, +* `InsufficientFunds`. + +In Spine, rejections are defined as Protobuf messages in the file which names ends with +`rejections.proto`. + +For detailed instructions on defining rejections, please refer to +[“Working with Rejections”]({{site.baseurl}}/docs/guides/rejections.html) guide. + +### Acknowledgement + +Acknowledgement is an outcome of sending a [Command](#command) to the [Command Service](#command-service). + +It tells whether the Command has been accepted for handling. + +### Command Handler + +Command Handler is an object which receives commands, modifies the state of the application, and +generates events if the modification was successful. + +[`Aggregate`](#aggregate) and [`ProcessManager`](#process-manager) are examples one of such classes. +The code snippet below given an example of handling a command by an aggregate: + +```java +final class TaskAggregate + extends Aggregate { + ... + @Assign + TaskCreated handle(CreateTask cmd, CommandContext ctx) { + return TaskCreated + .newBuilder() + .setTask(cmd.getId()) + .setName(cmd.getName()) + .setOwner(ctx.getActor()) + .vBuild(); + } + ... +} +``` + +### Event Subscriber + +Event Subscriber is an object which subscribes to receive events. + +The example below shows how a [Projection](#projection) class subscribed to the `TaskCompleted` +event. + +```java +final class TaskProjection + extends Projection { + ... + @Subscribe + void on(TaskCompleted e, EventContext ctx) { + builder().setWhenDone(ctx.getTimestamp()); + } +} +``` + +### Event Reactor + +Event Reactor is an object which usually produces one or more events in response to an incoming +event. Unlike [Event Subscriber](#event-subscriber), which always consumes events, a reacting object +generates events in response to changes in the domain. + +

In some cases, Event Reactor may ignore the event, returning `Nothing`. + It usually happens when a method returns one of the + [`Either`]({{site.core_api_doc}}/server/index.html) types, with `Nothing` as + one of the possible options: `EitherOf2`.

+ +## Value Objects + +Value Object describe things in a domain model and do not have identity. +Value Objects are also immutable. Some examples are: + * `PhoneNumber` + * `EmailAddress` + * `BarCode` + +In Spine, Value Objects are defined as Protobuf messages. + +## Entities + +Entities are the main building blocks of a domain model. +They have a unique identity and modify their state during their lifespan. + +### Identifier + +The framework supports the following types of identifiers: + +* `Integer`, +* `Long`, +* `String`, +* A generated Java class implementing the `Message`. + +Examples of entity IDs used by the framework: `CommandId`, `EventId`, `UserId`, `TenantId`. + +

We highly recommend using message-based IDs to make your API strongly pronounced + and type-safe.

+ +### Aggregate + +Aggregate is the main building block of a business model. +From the application point of view it consists of the following: +1. Commands which arrive to it. +2. Events which appear in response to these commands. +3. How these events influence the state of an aggregate. + +[Aggregates](http://martinfowler.com/bliki/DDD_Aggregate.html) guarantee consistency of data +modifications in response to commands they receive. Aggregate is the most common case of +Command Handler. In response to a command, it produces one or more events modifying own state. +These events are used later to restore the state of the aggregate. + +In Spine, aggregates are defined as Java classes, and their states are defined as Protobuf messages. + +### Process Manager + +Process Manager is an independent component that manages the cross-aggregate business flows. +It serves as a mediator by remembering the state of the flow and choosing the next step +based on the intermediate results. + +To do so, a Process Manager can be both [Command Handler](#command-handler) +and [Event Reactor](#event-reactor). Also, it can emit Commands to other Aggregates +and Process Managers. + +In Spine, Process Managers are defined as Java classes, and their states are defined as +Protobuf messages. + +### Projection + +Projection is an [Event Subscriber](#event-subscriber) which transforms multiple events data into +a structural representation. Projections are the main building blocks of the Query side of +the application. + +In Spine, Projections are defined as Java classes, and their states are defined as +Protobuf messages. + +### Repository + +Repository encapsulates storage, retrieval, and search of Entities as if it were +a collection of objects. It isolates domain objects from the details of the database access code. + +The applications you develop using Spine usually have the following types of repositories: +* [`AggregateRepository`]({{site.core_api_doc}}/server/io/spine/server/aggregate/AggregateRepository.html), +* [`ProcessManagerRepository`]({{site.core_api_doc}}/server/io/spine/server/procman/ProcessManagerRepository.html), +* [`ProjectionRepository`]({{site.core_api_doc}}/server/io/spine/server/projection/ProjectionRepository.html). + +### Snapshot + +Snapshot is a state of an Aggregate. A snapshot ”sits” in between events in the history of +the Aggregate to make restoring faster. + +When an Aggregate is loaded, the `AggregateStorage` reads events backwards until encounters +a snapshot. Then the snapshot is applied to the Aggregate, and trailing events are played +to obtain the most recent state. + +## Services + +Services are used by a client application for sending requests to the backend. + +In Spine, services are based on [gRPC](https://grpc.io). + +### Command Service + +The Command Service accepts a command from a client-side application and redirects it to +the [Bounded Context](#bounded-context) to which this command belongs. This means that there +is a context in which there is a [handler](#command-handler) for this command. Otherwise, +the command is not [acknowledged](#acknowledgement). + +### Query Service + +Query Service returns data to the client applications in response to a query. +The query is a request for the following: +* state of one or more aggregates or their fragments; +* one or more projection states or their fragments. +* one or more process manager states or their fragments. + +### Subscription Service + +Subscription Service allows to subscribe to something happening inside a Bounded Context. + +There are two options for subscription: +* receive changes of an Entity state for Projections, Process Managers and Aggregates; +* be notified of domain Events. + +## Architectural + +### Bounded Context + +Bounded Context is an autonomous component with its own domain model and its +own Ubiquitous Language. Systems usually have multiple Bounded Contexts. + +`Orders`, `UserManagement`, `Shipping` as examples of the contexts of an online retail system. + +### Message Buses + +#### Command Bus + +This is a message broker responsible for routing the command to its handler. +Unlike a [Command Handler](#command-handler), it does not modify the application business model, +nor produces domain events. + +There can be only one handler per command type registered in a Command Bus. + +#### Event Bus + +This bus dispatches events to entities that are [subscribed](#event-subscriber) to these +events or [react](#event-reactor) on them. + +### Message Stores + +#### Command Store + +This store keeps the history of all the commands of the application and statuses of +their execution. + +#### Event Store + +This store keeps all the events of the application in the chronological order, which also called +Event Stream. This is the main “database” of the Bounded Context. + +New projections are built by passing the event stream “through” them. + +### Integration Event + +Integration Events are [events](#event) used to communicate between different Bounded Contexts. + +In Spine, every domain Event may become an Integration Event, if it is emitted by the given +Bounded Context and consumed by another Bounded Contexts. + +### Aggregate Mirror + +In Spine, Aggregate Mirror contains the latest state of an Aggregate. +It “reflects” how it “looks” at the time of the last update. + +### Stand + +In Spine, Stand is a read-side API facade of a BoundedContext. + +### System Context + +System Context orchestrates the internal Spine framework entities that serve the goal of monitoring, +auditing, and debugging of domain-specific entities of the enclosing Bounded Context. +Users of the framework do not interact with this component. diff --git a/docs/introduction/diagrams/spine-architecture-diagram-full-screen.html b/docs/introduction/diagrams/spine-architecture-diagram-full-screen.html new file mode 100644 index 0000000..afa429c --- /dev/null +++ b/docs/introduction/diagrams/spine-architecture-diagram-full-screen.html @@ -0,0 +1,27 @@ +--- +layout: full-screen +title: Application Architecture +--- + +
+
+
+ + + +
+
+

Application Architecture

+

This diagram shows all + server-side components of a cloud application. + When developing with Spine, you will be interacting with + some of them.

+
+
+
+ {% include_relative spine-architecture-diagram.svg %} +
+
+ + + diff --git a/docs/introduction/diagrams/spine-architecture-diagram.svg b/docs/introduction/diagrams/spine-architecture-diagram.svg new file mode 100644 index 0000000..ffebcf3 --- /dev/null +++ b/docs/introduction/diagrams/spine-architecture-diagram.svg @@ -0,0 +1,576 @@ + + diagram-content + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bounded Context + + + + + System Context + + + System Context + + + + + + + + + + + + + + + + + + + + + + Aggregate + Repository + + + + + + + + + + + + + + + + + + + + Aggregate + + + + + Events + + + + + + + + + + + + + + + + + + + + + Projection + Repository + + + + + + + + + + + + + + + + + + + + Projection + + + + + + + + + + + + + + + + + + + + Process + Manager + Repository + + + + + + + + + + + + + + + + + + + + Process + Manager + + + + + + Event Bus + + + + + + Command Servic + e + + + + + + Query Sevic + e + + + + + + Subscription Servic + e + + + + + + Stand + + + + + + Command + Dispatche + r + + + + + + Aggregate Mirro + r + + + + + + + Event + Stor + e + + + + + + + Command + Stor + e + + + + + Aggregate States + + + + + + Integration + Events + + + + + + Queries + + + + + + Results + + + + + + Subscribe + + + + + + Updates + + + + + + Acks + + + + + Aggregate States + + + + + Client API + + + + + + Command Bus + + + + + + + + + + + + + + + + + Commands + + + + + + + + Write-side + + + Read-side + + + Cloud Application + + + Client Applications + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/introduction/index.md b/docs/introduction/index.md new file mode 100644 index 0000000..373c25b --- /dev/null +++ b/docs/introduction/index.md @@ -0,0 +1,255 @@ +--- +title: Development Process +headline: Documentation +bodyclass: docs +layout: docs +--- +# Development Process + +

Building a solution based on Spine Event Engine framework is an iterative process +which consists of the stages described in this document.

+ +## Getting domain knowledge + +The purpose of this step is to find out what we're going to build and why. +Consider using [EventStorming](https://eventstorming.com) or another domain discovery +approach for grasping the knowledge from the experts. + +Most likely that the solution would have several [Bounded Contexts](concepts.html#bounded-context). +For each context developers need to define: + * Signals + - [Events](concepts.html#event) + - [Commands](concepts.html#command) + - [Rejections](concepts.html#rejection) + * Entities + - [Aggregates](concepts.html#aggregate) + - [Process Managers](concepts.html#process-manager) + - [Projections](concepts.html#projection). + +It is likely that some of the bits of this picture would change during the process. +But the whole team, including domain experts, need to have complete understanding of how the +business works to avoid “surprises” down the road. + +We return to learning the domain when we discover inconsistencies in the model, +or we need more information about how the business works, or the business wants to develop further +and we need to update the model. + +Once we got enough domain knowledge we proceed to the implementation. + +## Implementing a Bounded Context + +At this stage we select one of the Bounded Contexts for the implementation. +Each context is developed separately. In this sense it can be seen as a microservice. +It would be natural to start implementing the context which initiates the business flow. + +### Defining data types + +Implementation starts from defining data types of the selected context as Protobuf messages. + +The first step is to define entity [IDs](concepts.html#identifier). For example: +```proto +// The identifier for a task. +message TaskId { + string uuid = 1; +} +``` + +Then commands, events, rejections are defined: +```proto +// A command to create a new task. +message CreateTask { + TaskId id = 1; + string name = 2 [(required) = true]; + string description = 3; +} +``` + +```proto +// A new task has been created. +message TaskCreated { + TaskId task = 1; + string name = 2 [(required) = true]; + string description = 3; + UserId who_created = 4 [(required) = true]; +} +``` + +Then we define states of entities. + +```proto +message Task { + option (entity).kind = AGGREGATE; + TaskId id = 1; + string name = 2 [(required) = true]; + string description = 3; + UserId owner = 4 [(required) = true]; + DeveloperId assignee = 5; +} +``` + +[Value Objects]({{site.baseurl}}/docs/introduction/concepts.html#value-objects) are added when they +are needed to describe entities or messages like commands or events. + +### Adding business logic + +The business logic of a Bounded Context is based on [Entities](#entities). +They handle messages updating the state in response. Entities like `Aggregate`s and +`ProcessManager`s can generate events. `ProcessManager`s can also generate new commands. +`Projection`s only consume events. + +Updating the state of the domain model in response to messages and generating new messages is +the “life” of the domain model. Messages are delivered to entities by [Repositories](#repositories). + +#### Entities + +During this step we create entity classes and add message handling methods to them. +Code snippets below show `Aggregate` and `Projection` classes with their handler methods. + +```java +final class TaskAggregate + extends Aggregate { + + @Assign + TaskCreated handle(CreateTask cmd, CommandContext ctx) { + return TaskCreated + .newBuilder() + .setTask(cmd.getId()) + .setName(cmd.getName()) + .setDescription(cmd.getDescription()) + .setWhoCreated(ctx.getActor()) + .vBuild(); + } + + @Apply + void event(TaskCreated e) { + builder().setName(e.getName()) + .setDescription(e.getDescription()) + .setOwner(e.getWhoCreated()); + } +} +``` + +```java +final class TaskItemProjection + extends Projection { + + @Subscribe + void on(TaskCreated e) { + builder().setTask(e.getTask()) + .setName(e.getName()) + } + + @Subscribe + void on(TaskCompleted e, EventContext ctx) { + builder().setWhenDone(ctx.getTimestamp()); + } +} +``` + +#### Repositories +The framework provides default implementations for repositories. +A custom `Repository` class may be needed for: + * Dispatching messages to entities in a non-standard way. + By default, a command is dispatched using the first field of the command message + as an ID of the target entity. + An event is dispatched by the ID of the entity which emitted the event. + * Domain-specific operations on entities of this kind. + +Repositories are added to the Bounded Context they belong when it is created: + +```java +BoundedContext tasksContext = BoundedContext.multiTenant("Tasks") + .add(TaskAggregate.class) // use default repository impl. + .add(new TaskItemProjectionRepository()) + .build(); +``` + +This wires repositories into the message delivery mechanism of the corresponding +[Buses]({{site.baseurl}}/docs/introduction/concepts.html#message-buses). + +#### Testing +Implementation of the Bounded Context is tested using the messaging paradigm. +The following code snippet asserts that handling a command `CreateTask` produces one +`TaskCreated` event with expected arguments. + +```java +// Given +BlackBoxBoundedContext context = BlackBoxBoundedContext.from(tasksContext); + +// When +context.receivesCommand(createTask()); + +// Then +TaskCreated expected = TaskCreated.newBuilder() + .setTask(id) + .setName(name) + .build(); + +EventSubject assertEvents = + context.assertEvents() + .withType(TaskCreated.class) + +assertEvents.hasSize(1); +assertEvents.message(0) + .comparingExpectedFieldsOnly() + .isEqualTo(expected); +``` + +Modification of entities is also tested. The following code snippet asserts that the state +of the `TaskAggregate` was also updated with expected arguments. + +```java +EntitySubject assertEntity = + context.assertEntityWithState(Task.class, id); +Task expectedState = Task.newBuilder() + .setId(id) + .setName(name) + .build(); +assertEntity.hasStateThat() + .comparingExpectedFieldsOnly() + .isEqualTo(expectedState); +``` + +## Deployment + +### Configuring Server Environment + +For information on configuring server environment of a Spine-based application, please +see the reference documentation of the [`ServerEnvironment`]({{site.core_api_doc}}/server/io/spine/server/ServerEnvironment.html) +class. + +### Assembling Application + +The server-side application is composed with its Bounded Contexts. + +```java +Server server = Server.atPort(portNumber) + .add(tasksContext) + .add(usersContext) + .add(commentsContext) + .build(); +server.start(); +``` + +This exposes [`CommandService`]({{site.baseurl}}/docs/introduction/concepts.html#command-service), +[`QueryService`]({{site.baseurl}}/docs/introduction/concepts.html#query-service), and +[`SubscriptionService`]({{site.baseurl}}/docs/introduction/concepts.html#subscription-service) to client-side connections. + +## Repeating the cycle + +The stages described above are repeated as another Bounded Context is added to the implementation, +or as some changes or extensions to the existing contexts are required. + +## Client application development + +Development of client applications may start after the [data types are defined](#defining-data-types). +Once this is done, developers run the Spine Model Compiler to generate the code for all supported +client platforms. + +Since entity classes like `Projection` use composition with state types, you don't have to deal +with DTOs. You can start writing the code which subscribes or queries these types right after +they are defied in the `.proto` files, and the Model Compiler finishes its job. +Client applications can also subscribe to events generated by Bounded Contexts at the backend. + +For more information on the client-side development, please refer to +the [Client Libraries]({{site.baseurl}}/docs/client-libs/) section. diff --git a/docs/introduction/naming-conventions.md b/docs/introduction/naming-conventions.md new file mode 100644 index 0000000..023af1e --- /dev/null +++ b/docs/introduction/naming-conventions.md @@ -0,0 +1,412 @@ +--- +title: Naming Conventions +headline: Documentation +bodyclass: docs +layout: docs +--- +# Naming Conventions + +

This document covers naming conventions for the code. Some of these conventions are +used by the framework and code generation, and as such are required. Others are our recommendations +that we find useful for making the code easier to understand.

+ +## Proto files + +Proto files are named using the `snake_case`, as defined by Protobuf. There are several special +kinds of files. + +### `identifiers.proto` + +Commands and events reference model entities using their identifiers. +Having typed identifiers makes a model type safe. +Although the framework also supports `String`, `Integer`, and `Long` as valid ID types, +we strongly recommend defining custom ID types like `CustomerId`, `OrderId`, `ShipmentId`, +and others. You can find similar cases in the framework API which has `EventId`, `CommandId`, +`UserId`, `TenantId`, and others. + +We find it convenient to define ID types in one file called `identifiers.proto`. +A typical project is likely to have more than one Bounded Context. Thus, you will have several +`identifiers.proto` files. +Each of them resides under the directory with proto files defining the data model of the +corresponding Bounded Context. For example: + +
+
+
+    myproject/
+      users/
+        src/
+          main/
+            java/
+            proto/
+              user.proto
+              group.proto
+              ...
+              identifiers.proto
+      tasks/
+        src/
+          main/
+            java/    
+            proto/
+              task.proto
+              project.proto
+              ...
+              identifiers.proto
+        ...
+
+
+
+ +### `commands.proto` + +Commands are defined in a file ending with `commands.proto`. +It can be simply `commands.proto` but usually commands are handled by different entities. +Thus, it is convenient to name such a file after the type of the target entity, +for example, an aggregate: + + * `blog_commands.proto` + * `order_commands.proto` + * `customer_commands.proto` + +### `events.proto` + +Similarly to commands, events are defined in files which names have the `events.proto` suffix: + + * `blog_events.proto` + * `order_events.proto` + * `customer_events.proto` + +### `rejections.proto` + +`Rejection` is a special “negative” kind of events supported by the framework. +A rejection is thrown if a command cannot be handled. You may think of them as of exceptions with +non-technical flavor. + +Similarly to events, rejections are defined in files ending with `rejections.proto`: + + * `blog_rejections.proto` + * `order_rejections.proto` + * `customer_rejections.proto` + +For each aggregate you are likely to have all three kinds of files because a command leads to +an event, and it is likely there are conditions under which a command cannot be handled. + +### Entity states + +We recommend gathering definition of related entity states in a file named after a business model +thing. Suppose we have a `Task` aggregate, `TaskItem` and `TaskDetails` projections, and +a Process Manager which is responsible for movement of a task from one project to another, there +would be `task.proto` file, with all Task-related data types definitions. A project-related data +types would be defined in a `project.proto` file. + +As it was already mentioned, `TaskId` and `ProjectId` are defined in the `identifiers.proto` file, +and `task.proto` and `project.proto` import this file. + +## Data types + +Data types are defined as Protobuf messages using `TitleCapitalization`. + +### Identifiers + +Identifiers are usually defined after the name of the entity with the `Id` suffix: + + * `ProjectId` + * `TaskId` + * `CommentId` + +You will find such naming pattern in the framework API. For example, `EventId`, `CommandId`, +`UserId`, `TenantId`, and others. + +

This convention is not a requirement. We find `Id` suffix short yet meaningful for + building a rich type-safe API. You can also select another convention that fits your domain + best. Please note that future version of the framework tools will use the `Id` suffix of the + types for code scaffolding and improving intelligence of code generation.

+ +While the identifier type is usually defined with the `Id` suffix, we do not recommend following +the same strategy for the proto field names. Naming fields as `id` or adding the `_id` suffix +is usually excessive because the identifier type already has the `Id` suffix. + +Instead, we name the fields by their respective type reference, so `user_id` becomes +`user` and `project_id` becomes `project`. + +
+
+

In events:

+{% highlight proto %} +message TaskCreated { + TaskId task = 1; + ProjectId project = 2; +} +{% endhighlight %} +
+
+

And entity states:

+{% highlight proto %} +message TaskProjection { + TaskId task = 1; + ProjectId project = 2 + string name = 3; +} +{% endhighlight %} +
+
+ +The only exception from the suggestion is when the ID is a part of the root aggregate state, +or a command that creates the aggregate directly. + +
+
+

In aggregates:

+{% highlight proto %} +message Task { + TaskId id = 1; + ProjectId project = 2; +} +{% endhighlight %} +
+
+

And entity creation commands:

+{% highlight proto %} +message CreateTask { + TaskId id = 1; + ProjectId project = 2; +} +{% endhighlight %} +
+
+ +### `repeated` and `map` fields + +We recommend naming `repeated` and `map` fields using singular nouns as such a naming appears +to be closer to the language we speak. It also provides easier to use generated code. + +

This convention contradicts with the official +[Protobuf Style Guide](https://developers.google.com/protocol-buffers/docs/style#repeated_fields +"Protocol Buffers Style Guide") which suggests naming `map` and `repeated` fields after plural +nouns. Knowing this, we still recommend singular because of the following. + +The code generated for a `repeated` and `map` field named after a singular noun is closer to +real English. For the code related to the Domain-Driven Design this is far more important than +the consistency with the style guide.

+ +So when defining `repeated` and `map` fields use: + +
+
+

Singulars

+{% highlight proto %} +message Task { + TaskId id = 1; + repeated SubTaskId subtask = 2; + map user_label = 3; +} +{% endhighlight %} +
+
+

Over pluralized names

+{% highlight proto %} +message CreateTask { + TaskId id = 1; + repeated SubTaskId subtasks = 2; + map user_labels = 3; +} +{% endhighlight %} +
+
+ +### Commands + +A command is defined as an imperative: + + * `CreateProject` + * `AssignTask` + * `RemoveComment` + +### Events + +Events are named as facts formulated as past participles, for example: + + * `ProjectCreated` + * `TaskAssigned` + * `CommentRemoved` + +### Rejections + +A rejection is named after a reason of why a command cannot be handled. In fact, rejection notifies +on a state of the domain model, for example: + + * `TaskAlreadyExists` + * `InsufficientFunds` + * `ProjectAlreadyCompleted` + +### Entity states + +Protobuf messages for entity states are defined using nouns, for example: + + * `Project` + * `Task` + * `Comment` + +Avoid using suffixes like `Aggregate`, `Projection`, `ProcessManager` when defining a proto type for +the following reasons: + + 1. You may want to use such a word when creating an entity Java class which _uses_ + a generated data type for holding the state of the entity. + 2. Such data structure does not represent a whole `Aggregate` or `ProcessManager` thing anyway. + It is just data. + +## Packages + +Packages allow to form namespaces for types and avoid clashes. It is customary to have a “root” +package for an organization or a service name. Most likely each Bounded Context would have +a dedicated package. + +Examples in this guide assume that a fictitious company called +[Acme Corporation](https://en.wikipedia.org/wiki/Acme_Corporation) creates a SaaS solution. +The company has a web presence site with the domain name `acme.io`. +The solution is a task management application called "Todo List" +which will be hosted at `todolist.acme.io`. + +### Proto packages + +Packages in Protobuf do not follow the reverse Internet domain name convention, +which is customary in Java. It would make sense to have a root package for all types defined +in an organization under the root package with a lowercase company name. + +For the fictitious SaaS project of the Acme Corporation it would be: + +```proto +package acme.todolist; +``` + +### Java packages + +Java does not have the notion of package nesting. Packages in Java are separated namespaces, +which seem hierarchical for convenience. When it comes to placing source code files +in a project, there is usually nesting formed by the directories in a file system. + +Spine framework uses this notion of “nesting” for marking multiple packages of a server-side code +[belonging to a Bounded Context]({{site.core_api_doc}}/core/io/spine/core/BoundedContext.html) +easier. But this is a convenience feature, not a requirement. + +Please see our recommendations for organizing generated and handcrafted code in sections below. + +#### Have package per data type + +It is easier to see everything related to a type, if all the generated code comes under a +“home” package of a data type. For example: + + * `io.acme.todolist.task` + * `io.acme.todolist.project` + * `io.acme.todolist.comment` + +This package would be a part of API shared between client- and server-side code of your +application. + +#### Commands + +We recommend putting command classes under a package which ends with `command`: + + * `io.acme.todolist.task.command` + * `io.acme.todolist.project.command` + * `io.acme.todolist.comment.command` + +The package name is singular because it reads better in a fully-qualified class name of +a command message. + +#### Events + +Similarly to commands, we recommend putting events generated by an entity under the `entity` +sub-package: + + * `io.acme.todolist.task.event` + * `io.acme.todolist.project.event` + * `io.acme.todolist.comment.event` + +#### Rejections + +Similarly to events, rejections are placed under the package called `rejection`: + + * `io.acme.todolist.task.rejection` + * `io.acme.todolist.project.rejection` + * `io.acme.todolist.comment.rejection` + +Unlike commands and events, rejection messages are generated under a file named `Rejections`. +The class is placed into a `rejection` package of the corresponding type. + +The package also contains generated `Throwable` _top-level_ classes that match rejection messages. +These classes are used in the `throws` clause of command handling methods. + +The arrangement with message classes nested under `Rejections` class and top-level `Throwable`s +is required to avoid name clashes while keeping these generated classes under the same package. + +

For details on rejections usage, refer to + [Defining Rejections Guide]({{site.baseurl}}/docs/guides/rejections.html).

+ +#### Server-side code + +To avoid unwanted dependencies we find it useful to put server-side code under +a sub-package called `server` with sub-packages for corresponding entity types: + + * `io.acme.todolist.server.task` + * `io.acme.todolist.server.project` + * `io.acme.todolist.server.comment` + +## Handcrafted Java Classes + +### Entities + +When naming entities we find it natural to start with a name of a state class and then +add a suffix which tells the type of the entity: + + * `ProjectAggregate` + * `OrderProcessManager` + * `TaskItemProjection` + +The suffix helps for observing together with other entities in a package. + +For process managers it may be enough to have the `Process` suffix dropping `Manager` +which frequently worked for us too. Other options for suffixes are `Pm` or `Procman`. + +

It would be a good idea to decide on such suffix as a team standard before you + start coding.

+ +#### Repositories + +We recommend _not_ using a type infix for naming repository classes. Alphabetical sorting would +make a repository class be next to an entity class, and you would not deal much with repository +classes anyway. Thus, it is just `SomethingRepository` rather than `SomethingAggregateRepository`: + + * `ProjectRepository` + * `OrderRepository` + * `TaskItemRepository` + +### Bounded Contexts + +#### Names + +Bounded Contexts names follow `TitleCapitalization` favoring plurals: + + * `Users` + * `Tasks` + * `DeliveredOrders` + +Although, singular names are perfectly fine too, for example: + + * `Billing` + * `Shipping` + * `DynamiteProduction` + +#### Packages + +If a name of a Bounded Context is used in a package, its name is transformed according to the rules +of a programming language. + +#### Factory classes + +A Java class that creates and configures an instance of a `BoundedContext` is named after the +name of the context with the `Context` prefix: + + * `UsersContext` + * `DeliveredOrdersContext` + * `OnboardingContext` diff --git a/docs/introduction/prior-art.md b/docs/introduction/prior-art.md new file mode 100644 index 0000000..d69f598 --- /dev/null +++ b/docs/introduction/prior-art.md @@ -0,0 +1,87 @@ +--- +title: Prior Art +headline: Prior Art +bodyclass: docs +layout: docs +--- + +# Prior Art + +The demands for software projects increase rapidly as time progresses. +So does the scope of architecture approaches to meet these needs. +This section will give you an overview of the concepts and implementations Spine has inherited, +while bringing some important differences into play. + +Spine is created for applications that follow the [CQRS](http://martinfowler.com/bliki/CQRS.html) +and [Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html) architectural patterns. + +Spine didn’t appear out of the blue. While working on our own CQRS/ES based projects we were +alarmed at how much manual effort is spent on creating events and commands, delivering events and data +to the web and mobile clients. It takes time, does not require much creativity from a developer, +whilst this energy could have been spent on productive +[Event Storming](http://ziobrando.blogspot.com/2013/11/introducing-event-storming.html), +detailing the Domain model and so on. Attempts to address this issue led to the Spine vision. + +A major addition Spine brings to the existent variety of tools, libraries, and frameworks is +automatic **code generation** for multiple application clients. +It is reached by using [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview). + +When creating an Event Sourcing application, you need to write classes for commands, events, +command handlers, aggregates, aggregate repositories, DTOs etc. +And if your organization wants an application on, let’s say, a couple of mobile platforms, +you would have to add a lot of work to deliver data to each client application. +So you need to make your code work on another platform by writing it in another language *manually*, +or translate it using tools like [J2ObjC](http://j2objc.org/), or resort to using only +Json in the client apps. + +Using [Protobuf](https://developers.google.com/protocol-buffers/docs/overview) for formulating +the business domain allows us to make this language +[ubiquitous](http://martinfowler.com/bliki/UbiquitousLanguage.html) not only in human interaction, +but in communication of computing devices too. + +**Immutability** is another major concept we follow. +Spine uses typed commands and events. Having commands and events as first class citizens in the +applications gives a lot of benefits in terms of business logic. Not having to convert back and +forth with Json gives some performance advantage at the same time. + +We are greatly inspired by [Redux](http://redux.js.org) — one of the most exciting things happening +in JavaScript at the moment. It stands out from the landscape of libraries and frameworks by +getting so many things absolutely right: a simple, predictable state model; an emphasis on functional +programming and immutable data. + +In Spine Event Engine we combined all of our experience and observations of the best-breed market +products and solutions like [Axon](http://www.axonframework.org/), [Spring](https://spring.io/), +[Event Store](https://geteventstore.com/), [InfluxDB](https://influxdata.com/), +[Apache Zest](https://zest.apache.org/) and many others. +Spine has yet to find its own niche. + +Spine probably won’t be the best fit for trading or highly loaded applications, where, for example, +[LMAX](https://www.lmax.com/) does an excellent job. Our motivation is to make development of +modern applications easier and more efficient, and to offer a set of practical solutions to bring +this into life with a corresponding approach and terminology. + +In terminology we heavily lean on [Domain-Driven Design (DDD)](https://en.wikipedia.org/wiki/Domain-driven_design) +and the [“Big Blue Book”](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) +by Eric Evans. + +We learned a lot from the book [“CQRS Journey”](https://msdn.microsoft.com/en-us/library/jj554200.aspx) +by Microsoft, and our choice of the term “Process Manager” over the commonly used “Saga” is +based on the experience of Microsoft engineers. +The “Process Manager” pattern was first defined and brought into common vocabulary by Kyle Brown +and Bobby Woolf under the guidance of Martin Fowler in the book +[“Enterprise Integration Patterns”](http://www.enterpriseintegrationpatterns.com/patterns/messaging/ProcessManager.html). + +Another great resource on object-oriented design worth mentioning here is +[“Patterns of Enterprise Application Architecture”](http://www.martinfowler.com/books/eaa.html) +by Martin Fowler. Many modern frameworks implement these patterns behind the scenes, and so does Spine. +But as [Martin Fowler](http://www.martinfowler.com/books/eaa.html) notes: + + >Frameworks still require you to make decisions about how to use them, + >and knowing the underlying patterns is essential if you are to make wise choices. + +Systems built on top of Spine framework are flexible, loosely-coupled, scalable and open to change. +Here we should thank [Reactive Manifesto](http://www.reactivemanifesto.org/), +which became one the corner stones and drivers of the Spine philosophy. + +We are yet at the beginning of our journey of using Spine in the wild. +Join us and share how it goes! diff --git a/docs/introduction/project-structure.md b/docs/introduction/project-structure.md new file mode 100644 index 0000000..37c4231 --- /dev/null +++ b/docs/introduction/project-structure.md @@ -0,0 +1,92 @@ +--- +title: Project Structure +headline: Documentation +bodyclass: docs +layout: docs +--- +# Project Structure + +

This document describes standard structure of a Spine-based project. +It assumes that you are familiar with Gradle.

+ +

Spine uses Gradle for project model definition and as the build tool. +It follows the standard structure of the Gradle project with extensions related to +the code generation done by Protobuf Compiler and Spine Model Compiler.

+ +## Handcrafted code + +Following standard Gradle conventions a manually written code is created under the +`src/main/` directory with subdirectories `proto`, `java`, etc. for corresponding languages. + +After a project is defined in Gradle, a work on a module usually starts in the +the `proto` directory. + +## Generated code + +The generated code is placed under the `generated` directory under the module root. +The sub-directories are: + +* `java` — the code generated by Protobuf Compiler +* `resources` — mappings generated by Spine Model Compiler +* `spine` — the code generated by Spine Model Compiler + +### Excluding from version control + +The generated code is created and updated during build time. Directories with the generated +code files should NOT be added to version control system of your project. +This makes a commit to contain only essential changes relevant to the update of the model, +in particular: + + 1) Modifications of `.proto` files of the data model. + + 2) Updated calls from the application code to the generated data model API. + +By not including the generated code into the version control we minimise the “noise” +for developer eyes when a model changes. So, if you are using Git, for example, consider adding + the following line to your `.gitignore` file: + +``` +**/generated/** +``` + +## Example + +Here's how a typical project structure would look like: + +``` +myproject/ + gradle/ + module-one/ + generated/ + main/ + java/ + resources/ + spine/ + ... + test/ + java/ + resources/ + spine/ + ... + src/ + main/ + java/ + proto/ + test/ + java/ + proto/ + ... + build.gradle + module-two/ + generated/ + ... + src/ + ... + build.gradle + ... + build.gradle + gradlew + gradlew.bat + settings.gradle +``` + diff --git a/docs/introduction/rules.md b/docs/introduction/rules.md new file mode 100644 index 0000000..1be93cc --- /dev/null +++ b/docs/introduction/rules.md @@ -0,0 +1,22 @@ +--- +title: Rules +headline: Documentation +bodyclass: docs +layout: docs +type: markdown +--- + +# Rules + +Here are the ground rules the framework is built upon: + +1. An update to a business model is an event. + +2. Entities are changed in response to events. + +3. A command has one and only one handler. + +4. A command must result in an event, a rejection, or other commands. + +5. Events are always appended. Never deleted or edited. + diff --git a/docs/quick-start/index.md b/docs/quick-start/index.md new file mode 100644 index 0000000..c51a733 --- /dev/null +++ b/docs/quick-start/index.md @@ -0,0 +1,996 @@ +--- +title: Getting Started in Java +headline: Documentation +bodyclass: docs +layout: docs +next_btn: + page: Development Process Overview +--- + +# Getting started with Spine in Java + +

This guide will walk you through a minimal client-server application in Java +which handles one command to print some text on behalf of the current computer user. The document +goes through already written code which is quite simple. So, it won't take long. +

+ +## What we'll do + +We'll go through the example which shows a Bounded Context called “Hello”. +The context has one `ProcessManager`, which handles the `Print` command +sent from the client-side code to the server-side code hosting the context. +We'll go through the production code of the example suite, and through the code which +tests the Hello context. + +## What you'll need + +1. JDK version 8 or higher. +2. Git. +3. The source code of the [Hello World](https://github.com/spine-examples/hello) example. + + ```bash + git clone git@github.com:spine-examples/hello.git + ``` + +## Run the code + +To check that you've got everything installed, please run the following command: + +```bash +./gradlew :sayHello +``` + +If you're under Windows, it would be: + +```bat +gradlew.bat :sayHello +``` + +This would build and execute the example. +The process should finish with the output which looks like this: + +``` +> Task :sayHello +Dec 18, 2020 5:32:11 PM io.spine.base.Environment setCurrentType +INFO: `Environment` set to `io.spine.base.Production`. +Dec 18, 2020 5:32:12 PM io.spine.server.Server lambda$start$1 +INFO: In-process server started with the name `a7c62b63-2cc6-4679-bb92-072591142275`. +[savik] Hello World! +The client received the event: io.spine.helloworld.hello.event.Printed{"username":"savik","text":"Hello World!"} +Dec 18, 2020 5:32:15 PM io.spine.server.Server shutdown +INFO: Shutting down the server... +Dec 18, 2020 5:32:15 PM io.spine.server.Server shutdown +INFO: Server shut down. +``` + +The first line tells which Gradle task we run. The following couple of lines is the server-side +logging that informs us that the server was started. + +The line with the `Environment` tells that we're running the application in the `Production` +environment. +The line with “Hello World!” text is the “meat” of this example suite. +It is what our `ProcessManager` (called `Console`) does in response to the `Print` command received +from the `Client`. +The text in between brackets is the name of the current computer user. The name was passed as +the argument of the `Print` command. + +

We opted to show a `ProcessManager` — instead of an `Aggregate` — because +the console output is similar to an “External System”. Dealing with things like +that is the job of Process Managers. We also want to highlight the importance of using +this architectural pattern.

+ +The output that follows is the logging produced by the `Client` class as it receives the `Printed` +event from the server. + +Then, the server shuts down concluding the example. + +Now, let's dive into the code. + +## Project structure + +For the sake of simplicity, this example is organised as a single-module Gradle project. +Most likely, a project for a real world application would be multi-module. + +### The root directory + +The root of the project contains the following files: + * `LICENSE` — the text of the Apache v2 license under which the framework and + this example are licensed. + * `README.md` — a brief intro for the example. + * `gradlew` and `gradlew.bat` — scripts for running Gradle Wrapper. + * **`build.gradle`** — the project configuration. We'll review this file later + [in details](#adding-spine-to-a-gradle-project). + +

The root directory also contains “invisible” files, names of which start with +the dot (e.g. `.gitattributes` and `.travis.yml`). +These files configure Git and CI systems we use. They are not directly related to the subject +of the example and this guide. If you're interested in this level of details, +please look into the code and comments in these files. +

+ +Here are the directories of interest in the project root: + * `gradle` — this directory contains the code of Gradle Wrapper and two Gradle scripts + used in the [project configuration](#other-project-configuration). + * **`generated`** — this directory contains the code generated by Protobuf Compiler and + Spine Model Compiler. This directory and code it contains is created automatically + when a domain model changes. This directory is excluded from version control. + * **`src`** — contains the handcrafted source code. + +Let's see how the source code is structured. + +### The `src` directory + +The source code directory follows standard Gradle conventions and has two sub-directories: + * **`main`** — the production code; + * **`test`** — the tests for the production code. + +The production code consists of two parts allocated by sub-directories: + * **`proto`** — contains the definition of data structures in Google Protobuf. + A domain model definition starts from adding the code to this directory. + Then, the Protobuf code is compiled to the languages of the project. + The output of this process is placed into the `generated` directory, with a sub-directory + for each language. This example uses only Java. + + * **`java`** — this directory contains the model behavior and other server- and client-side code. + A real project would have these parts in separate modules or projects. We put it all + together for the sake of simplicity. + +Now, let's review the code in details, starting with how to add Spine to a Gradle project. + +## Adding Spine to a Gradle project + +Let's open `build.gradle` from the root of the project. The simplest and recommended way for +adding Spine dependencies to a project is the Bootstrap plugin: + + +```gradle +plugins { + id("io.spine.tools.gradle.bootstrap").version("1.7.0") +} +``` + +Once the plugin is added, we can use its features: + + +```groovy +spine.enableJava().server() +``` + +This enables Java in the module and adds necessary dependencies and configurations. + +

Calling `spine.enableJava().server()` adds both server- and client-side dependencies. +This way a module of a Bounded Context “A” may be a client for a Bounded Context “B”. +Client-side applications or modules should call: `spine.enableJava().client()`. +

+ +### Other project configuration + +The rest of the `build.gradle` file does the following: + 1. Sets the version of Java to 8. + + 2. Adds JUnit dependencies by applying the `tests.gradle` script plugin (which we extracted + for the sake of simplicity). + + 3. Defines the `sayHello` task which runs the `Example` application, which orchestrates + the demo. + +We are not reviewing these parts of the project configuration deeper because they are not +related to the use of the Spine framework. If you're interested in more details, please look into +the mentioned `.gradle` files. + +Now, let's look into the data structure of the Hello context. + +## Hello context data + +The data types of the Hello context are defined under the `src/main/proto/hello` directory with +the following files: + + * **`commands.proto`** — this file defines the `Print` command. + +

By convention, commands are defined in a file with the `commands` suffix + in its name. It can be, for example, `order_commands.proto` or just `commands.proto` + like in our example.

+ + * **`events.proto`** — this file defines the `Printed` event. + +

Similarly to commands, events are defined in proto files having the `events` + suffix in their names.

+ +These two files define signals used by the Hello context. There's also data of the `Console` +Process Manager, which is defined in the package **`server`** in the file **`console.proto`**. + +

+We arrange the sub-package `server` to highlight the fact that this is server-only data. It is not +a convention used by the framework. We find the clarity of this infix useful when creating +cloud applications. So, we share it as a recommendation in this example.

+ +Let's review the context data definition in details. + +### The `commands.proto` file + +After the copyright header the file starts with the syntax declaration. The framework supports only +this version of the Protobuf dialect: +```proto +syntax = "proto3"; +``` + +Then follows the import statement for custom options used when defining data types. This import is +required for all proto files of a Spine-based project. + + +```proto +import "spine/options.proto"; +``` + +The following file-wide option defines the prefix for type names used in this file. + + +```proto +option (type_url_prefix) = "type.spine.io"; +``` + +This prefix is needed for recognizing binary data when it's unpacked. Most likely, all types +in a system would use the same prefix. + +Then we see the standard Protobuf option for defining a Java package for the generated code: + + +```proto +option java_package="io.spine.helloworld.hello.command"; +``` + +There are three parts of interest in this package name: + * **`io.spine.helloworld`** — this part represents the location of our Hello World “solution” + as if it were hosted on the web at `https://helloworld.spine.io`. + + * **`hello`** — this is the package of the Hello context. In this example we have only one, but + a real world app would have more. Each Bounded Context goes into a dedicated package. + + * **`command`** — this part gathers commands of the context under one package. + We have only one command in this example, but in real world scenarios, with dozens of commands, + it is convenient to gather them under a package. + +The following standard proto file option defines the name for the outer class generated by +Protobuf Compiler for this `.proto` file: + + +```proto +option java_outer_classname = "CommandsProto"; +``` + +

Outer classes are used by Protobuf implementation internally. +When the `java_outer_classname` option is omitted, Protobuf Compiler would calculate the Java +class name taking the name of the corresponding `.proto` file. +We recommend setting the name directly to make it straight. This also avoids possible name clashes +with the handcrafted code.

+ +The next standard option instructs the Protobuf Compiler to put each generated Java type into +a separate file. This way it would be easier to analyze dependencies of the code which uses these +generated types. + + +```proto +option java_multiple_files = true; +``` + +The command for printing a text in a console is defined this way: + + +```proto +message Print { + + // The login name of the computer user. + string username = 1; + + // The text to print. + string text = 2 [(required) = true]; +} +``` + +By convention, the first field of a command is the ID of the target entity. +This field is required to have a non-empty value so that the command can be dispatched to +the entity. In our case, we identify a console by the login name of the computer user. + +The second field is marked as `(required)` using the custom option imported in +the `spine/options.proto` file above. This command does not make much sense if there is no text +to print. + +

In Protobuf a data type is either a **`message`** (we can send it) or an **`enum`**. +If you're new to this language, you may want to look at the [Proto3 Language Guide][proto3-guide]. +

+ +Now, let's see how to define events. + +### The `events.proto` file + +Events are declared similarly to commands. The header of the file has: + + * The `proto3` syntax declaration. + * The `hello` package declaration. + * The import statement for custom Protobuf options used by the framework: + + ```proto + import "spine/options.proto"; + ``` + + * The same `(type_url_prefix)` we use in this project. + * A separate Java package for events of the context: + + ```proto + option java_package="io.spine.helloworld.hello.event"; + ``` + + * The outer Java class for all types in this file: + + ```proto + option java_outer_classname = "EventsProto"; + ``` + + * The instruction to put Java types into separate files. + + ```proto + option java_multiple_files = true; + ``` + +The sole event in this project is declared this way: + + +```proto +message Printed { + + // The login name of the user. + string username = 1 [(required) = true]; + + // The printed text. + string text = 2 [(required) = true]; +} +``` + +The event tells which text was printed for a user. Both of the fields are marked as `(required)` +because the event does not make much sense if one of them is empty. + +

+Unlike for commands, the framework does not assume that the first event field is always +populated. This is so because default routing rules for commands and events are different. +When an event is produced by some entity, it remembers the ID of this producer entity. +By default, the framework uses the producer ID to route events to their target entities — +if they have identifiers of the same type. If the type of producer ID does not match one of the +target entity, then event fields are analyzed. It is also possible to set custom routing rules. +

+ +Now, let's see the server-side data of the Hello context. + +### The `console.proto` file + +The header of the file is similar to those we saw in `commands.proto` and `events.proto`. +The difference is that we use `server` for the proto and Java package names to make sure the +server-only is not used by the client code. + +This file defines a single data type. It is the state of the entity handling the `Print` command: + + +```proto +message Output { + option (entity) = { kind: PROCESS_MANAGER }; + + // The login name of the computer user. + string username = 1; + + // Text lines of the screen. + repeated string lines = 2; +} +``` + +The option `(entity)` tells us that this type is going to be used by a `ProcessManager`. +The first field of the type holds the ID of this entity. The framework assumes such fields as +implicitly `(required)`. Then goes the declaration of the remote screen output. The value of this +field is empty until something is printed on the screen. Therefore, it is not marked `(required)`. + +Now, let's see how this data is used at the server-side. + +## The `Console` class + +The class is declared this way: + + +```java +final class Console extends ProcessManager { +``` + +The generic arguments passed to `ProcessManager` are: + 1. `String` — the type of the ID of the entity. Remember the type +of the first field of the `Print` command? + + 2. `Output` — the type of the entity state, which we reviewed in the previous section. + + 3. `Output.Builder` — the type used to modify the state of the entity. + In Protobuf for Java, each message type has a specific builder type. + +### Handling the `Print` command + +The command is handled by this method: + + +```java +@Assign +Printed handle(Print command) { + String username = command.getUsername(); + String text = command.getText(); + builder().setUsername(username) + .addLines(text); + println(username, text); + return Printed.newBuilder() + .setUsername(username) + .setText(command.getText()) + .vBuild(); +} +``` + +Let's review the method in details. + +The `@Assign` annotation tells that we assign this method to handle the command which the +method accepts as the parameter. The method returns the event message `Printed`. + +The following code obtains the name of the user, and the text to print from the received command, +and then applies them to the state of the Process Manager. Instances of the `Console` class store +the text printed for each user. + + +```java +String username = command.getUsername(); +String text = command.getText(); +builder().setUsername(username) + .addLines(text); +``` + +Then we print the text to `System.out` so that it becomes visible on the screen: + + +```java +println(username, text); +``` + +This is done by the method the `Console` class declares: + + +```java +private void println(String userName, String text) { + String output = format("[%s] %s", userName, text); + System.out.println(output); +} +``` + +Then, the command-handling method concludes by producing the event message: + + +```java +return Printed.newBuilder() + .setUsername(username) + .setText(command.getText()) + .vBuild(); +``` + +The `vBuild()` call validates and builds the message. This method is generated by Spine Model +Compiler. For instructions on adding validation attributes to your model please see +[Validation User Guide]({{site.baseurl}}/docs/guides/validation.html). + +

After the event is generated, it is posted to the `EventBus` and delivered to +subscribers automatically. You don't need to write any code for this.

+ +Now, let's see how the `Console` Process Manager is exposed to the outer world so that it can +receive commands. + +## Assembling the Hello context + +Let's open the `HelloContext` class. The first thing of interest in this class is the declaration of +the name of the Bounded Context: + + +```java +static final String NAME = "Hello"; +``` + +This constant is used for creating the context (we will review it in a minute) and when annotating +the server-side code which belongs to the Hello context. This is done in `package-info.java` using +the `@BoundedContext` annotation: + + +```java +@BoundedContext(HelloContext.NAME) +package io.spine.helloworld.server.hello; +``` + +

The framework assumes that all entity classes belonging to this and nested packages +belong to the Bounded Context with the name specified in the argument of the annotation. +This arrangement is needed for routing events.

+ +The second thing the `HelloContext` does is creating a Builder for the Bounded Context: + + +```java +public static BoundedContextBuilder newBuilder() { + return BoundedContext + .singleTenant(NAME) + .add(Console.class); +} +``` + +The context we create is single-tenant. It contains one entity type which we pass to the builder. + +

If an entity uses default routing rules for the incoming events and commands, +its type can be added to `BoundedContextBuilder` directly. If custom routing rules are needed, +they are specified by a custom `Repository` class. In this case, an instance of such `Repository` +is passed to `BoundedContextBuilder` instead of the entity type managed by this `Repository`.

+ +Once we assembled the Bounded Context, let's test it. + +## Testing the Hello context + +Let's open the `HelloContextTest` suite. It is based on JUnit 5 and `spine-testutil-server` library. + +

We already added JUnit dependency when defining the Gradle project. +The `testImplementation` dependency for `spine-testutil-server` is automatically added when +you enable Spine in your project using `spine.enableJava().server()`. So, we're good to go testing.

+ +The class of the test suite extends the abstract base called `ContextAwareTest`: + + +```java +@DisplayName("Hello context should") +class HelloContextTest extends ContextAwareTest { +``` + +The base class is responsible for creation of a test fixture for a Bounded Context under +the test before each test, and for disposing the fixture after. The fixture is accessed using +the `context()` method. + +We pass the Hello Context for testing using its builder by implementing the abstract method +`contextBuilder()` inherited from `ContextAwareTest`: + + +```java +@Override +protected BoundedContextBuilder contextBuilder() { + return HelloContext.newBuilder(); +} +``` +Now we can get down to the tests. The suite verifiers the outcome of the `Print` command. +The test methods are gathered under the nested class called +`PrintCommand`. The class holds the reference to the command as its field: + + +```java +@Nested +@DisplayName("handle the `Print` command") +class PrintCommand { + + private Print command; +``` + +### Sending the command + +The command is created and sent to the test fixture before each test method: + + +```java +@BeforeEach +void sendCommand() { + command = Print.newBuilder() + .setUsername(randomString()) + .setText(randomString()) + .vBuild(); + context().receivesCommand(command); +} +``` + +For test values we use statically imported `randomString()` method of the `TestValues` utility class +provided by the `spine-testutil-server` library. We use the `context()` method provided by +`ContextAwareTest` for obtaining the reference of the test fixture of the Bounded Context +under the test. + +Now we need to test that handling the command produces the event, and the handling entity updates +its state. + +### Testing creation of the event + +Testing that an event was generated is quite simple. We create the expected event and assert it +with the test fixture: + + +```java +@Test @DisplayName("emitting the `Printed` event") +void event() { + Printed expected = Printed.newBuilder() + .setUsername(command.getUsername()) + .setText(command.getText()) + .build(); + context().assertEvent(expected); +} +``` + +### Testing entity state update + +For testing the state of the `Console` Process Manager was updated, we construct the expected +state and pass it to the `assertState()` method of the test fixture: + + +```java +@Test @DisplayName("updating the `Console` entity") +void entity() { + Output expected = Output.newBuilder() + .setUsername(command.getUsername()) + .addLines(command.getText()) + .vBuild(); + context().assertState(command.getUsername(), expected); +} +``` + +The first argument passed to the `assertState()` method is the ID of the entity the state of which +we test. The second argument is the expected state. + + +Now as we've checked that our Bounded Context works correctly, let's expose it in +the server-side application. + +## The server-side application + +Let's open the `Server` class of our example application suite. The static initialization of +the class configures the server environment: + + +```java +static { + configureEnvironment(); +} +``` + +### Configuring the environment + +The `configureEnvironment()` method initializes the `Production` environment of this example with +the settings that are normally used for testing: + + +```java +private static void configureEnvironment() { + ServerEnvironment.when(Production.class) + .use(InMemoryStorageFactory.newInstance()) + .use(Delivery.localAsync()) + .use(InMemoryTransportFactory.newInstance()); +} +``` + +A real-world application would use `StorageFactory` and `TransportFactory` instances that correspond +to a database, and a messaging system used by the application. + +### The constructor + +The implementation of the `Server` class wraps around the class `io.spine.server.Server` provided +by the framework. This API is for exposing `BoundedContext`s in a server-side application. +This is what our `Server` class does in the constructor: + + +```java +public Server(String serverName) { + this.server = inProcess(serverName) + .add(HelloContext.newBuilder()) + .build(); +} +``` + +The constructor accepts the name which is used for connecting clients. This name +is passed to the `inProcess()` factory method of the `io.spine.server.Server` class. + +

In-process gRPC communications are normally used for testing. +This example uses in-process client/server arrangement in the production code for +the sake of simplicity. A real-world application would use a `Server` instance exposed +via a TCP/IP port.

+ +Once we have the `Server.Builder` instance returned by the `inProcess()` method, +we add the Hello Context via its builder to the constructed `Server` instance. + +### Start and shutdown + +The remaining code of our `Server` class declares `start()` and `shutdown()` methods that +simply delegate calls to the wrapped `io.spine.server.Server` instance. + +Now we have the server, but how does the client side look like? + +## The client code + +Similarly to `Server`, the `Client` class of our example application wraps around +the `io.spine.client.Client` API provided by the `spine-client` library: + + +```java +public final class Client { + + private final io.spine.client.Client client; +``` + +

The `io.spine:spine-client` library is provided +to the example application project as a transitive dependency of +the `io.spine:spine-server` library, which is added to the project when you do +`spine.enableJava().server()` in your Gradle project.

+ +Then, the `Client` class declares a field for keeping subscriptions to the results of a command +execution. We'll see how this field is used in a minute. + + +```java +private @Nullable ImmutableSet subscriptions; +``` + +First of all, let's see how the `Client` instances are created. + +### The constructor + + +```java +public Client(String serverName) { + this.client = inProcess(serverName) + .shutdownTimeout(2, TimeUnit.SECONDS) + .build(); +} +``` + +Again, similarly to a `Server`, a `Client` is created using in-process connection to a named server. +The `shutdownTimeout` parameter configures the amount of time allowed for completing ongoing +client-server communications. + +Now, let's review the main thing the `Client` class does, sending the `Print` command. + +### Sending the command + + +```java +public void sendCommand() { + String userName = System.getProperty("user.name"); + Print commandMessage = + Print.newBuilder() + .setUsername(userName) + .setText("Hello World!") + .vBuild(); + this.subscriptions = + client.asGuest() + .command(commandMessage) + .observe(Printed.class, this::onPrinted) + .post(); +} +``` + +The method does three things: + 1. Creates a command message using the login name of the current computer user. + 2. Subscribes to the `Printed` event which will be generated as the result of + handling the command we are going to post (and only this instance of the command, + not all `Print` commands). + 3. Posts the command. + +Let's review subscribing and posting in details. + +The `client.asGuest()` call starts composing a client request. For the sake of simplicity, +we do this on behalf of a user who is not logged in. A real-world application would send +most of the commands using `client.onBehalfOf(UserId)`. + +The `command(commandMessage)` call tells we want to send a command with the passed message. + +Then, we subscribe to the `Printed` event which would be generated as the result of handling +the command. The events obtained from the server would be passed to the `onPrinted()` method +of the `Client` class. + +The `post()` method sends the command to the server, returning the set with one `Subscription` +to the `Printed` event. We store the returned set in the field to use later for cancelling +the subscription. + +### Handling the event + +When the client code receives the `Printed` event, it prints its data and then +cancels the subscription: + + +```java +private void onPrinted(Printed event) { + printEvent(event); + cancelSubscriptions(); +} +``` + +There's not much exciting about the printing part. + + +```java +private void printEvent(EventMessage e) { + String out = format( + "The client received the event: %s%s", + e.getClass() + .getName(), + toCompactJson(e) + ); + System.out.println(out); +} +``` + +The only interesting thing here is the statically imported `Json.toCompactJson()` call which +converts the event message to a `String`. The method is available from the `Json` utility class +provided by the framework. The utility does heavy lifting of the conversion which involves knowing +all Protobuf types available in the project. + +Cancelling the subscription, if any, iterates through the set passing each of them to +the `Client.subscriptions()` API for the cancellation: + + +```java +private void cancelSubscriptions() { + if (subscriptions != null) { + subscriptions.forEach(s -> client.subscriptions().cancel(s)); + subscriptions = null; + } +} +``` + +Once we finish with the cancellation, we clear the `subscriptions` field. + +### Closing the client + +The `close()` method simply delegates to the method of the `io.spine.client.Client` class. +We also need to tell the calling code if the client has finished its job. +This is what the `isDone()` method is for: + + +```java +public boolean isDone() { + return client + .subscriptions() + .isEmpty(); +} +``` + +The method checks active subscriptions. If there are none, we got the event and cleared its +subscription. + +Now, let's put it all together. + +## Orchestrating the example + +Let's review the `Example` class and its `main()` method. It simulates client-server communication +scenario. + + +```java +public static void main(String[] args) { + String serverName = Identifier.newUuid(); + Server server = new Server(serverName); + Client client = null; + try { + server.start(); + client = new Client(serverName); + client.sendCommand(); + while (!client.isDone()) { + sleepUninterruptibly(Duration.ofMillis(100)); + } + } catch (IOException e) { + onError(e); + } finally { + if (client != null) { + client.close(); + } + server.shutdown(); + } +} +``` + +The first thing the method does is creating a `Server` using a random name. + +Then, the server is started. It is done in the `try/catch/finally` block because the `start()` +method may throw `IOException` in case of communication problems reported by gRPC. +The `catch` block calls the `onError()` method of the `Example` class which simply prints +the exception. A real-world application would use more sophisticated exception handling. + +After the `Server` is started, we create a `Client` using the name of the server generated before. +Then, the client sends the command, and we wait until the client finishes its job using +the statically imported method `sleepUninterruptibly()` provided by Guava. + +The `main()` method finishes by closing the client, and shutting down the server. + +## Summary + +Although the “business case” of this example is trivial, it shows basic steps of development +of a solution based on Spine Event Engine framework. + +The development starts with the discovery of the business using EventStorming or another +learning approach. + +Then, we select a Bounded Context and define events, commands, Value Objects, and entity states +using Protobuf. Using these `.proto` files Spine Model Compiler generates the code implementing +the defined data types. + +After that, we add the business logic for handling commands or events in entity classes derived from +`Aggregate`, `ProcessManager`, or `Projection`. + +Then, these entity types are assembled into a Bounded Context and tested as a whole. +A test suite sends signals (i.e. commands or events) to the implementation of the Bounded Context +and verifies the changes manifested as events or new entity states. + +Once the Bounded Context is tested, it is added to a `Server` which gathers all the Bounded Contexts +of the solution and accepts incoming requests from the client applications. + +Client applications send commands to modify the business model and subscribe to messages +like events that reflect the changes of the model. + +Once handling of all commands and events of the selected Bounded Context is done, another +context is selected for the development. The process is repeated until all the contexts +are implemented. + +[proto3-guide]: https://developers.google.com/protocol-buffers/docs/proto3 diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 0000000..368e8fc --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,26 @@ +--- +title: API Reference +headline: Documentation +bodyclass: docs +layout: docs +--- +# API Reference + +## Java + +- [Server]({{site.core_api_doc}}/server/index.html) + +- [Client]({{site.core_api_doc}}/client/index.html) + +- [Spine Web API]({{site.web_api_doc}}/web/index.html). +This reference defines the API for communicating with a Spine-based backend using web requests. + +- [Web API implementation on Firebase]({{site.web_api_doc}}/firebase-web/index.html). +This reference is on the implementation of the Spine Web API using the Firebase Realtime Database +to deliver data to web clients. + +## JavaScript +- [JavaScript Client]({{site.js_api_doc}}/index.html) + +## Dart +- [Dart Client]({{site.dart_api_doc}}/client/index.html) diff --git a/docs/resources/blogs.md b/docs/resources/blogs.md new file mode 100644 index 0000000..e9e2e60 --- /dev/null +++ b/docs/resources/blogs.md @@ -0,0 +1,18 @@ +--- +title: Blogs +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +# Blogs + +

The ideas unfolding in real time

+ +- [Domain Driven Design Weekly](http://dddweekly.com/). +The periodical digest of articles and materials on DDD by Nick Chamberlain. + +- [Martin Fowler on Domain-Driven Design](https://martinfowler.com/tags/domain%20driven%20design.html). + +- [Avanscoperta blog](https://blog.avanscoperta.it/it/). +The blog by Alberto Brandolini and his colleagues. diff --git a/docs/resources/books.html b/docs/resources/books.html new file mode 100644 index 0000000..01e658c --- /dev/null +++ b/docs/resources/books.html @@ -0,0 +1,84 @@ +--- +title: Books +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +

Books

+

Key works to get familiar with the approach

+ +
+ + Domain-Driven Design book + +
+ +

Domain-Driven Design

+

Tackling Complexity in the Heart of Software

+
+

by Eric Evans

+

The Big Blue Book which lay the basics of Domain-Driven Design methodology. + It provides a broad framework for making design decisions and a vocabulary for + discussing domain design.

+
+
+ +
+ + Domain-Driven Design book + +
+ +

Domain-Driven Design Reference

+
+

by Eric Evans

+

This book provides a brief overview of the DDD methodology. + It also defines the terms, which were coined in this topic since 2004.

+
+
+ +
+ + Implementing Domain-Driven Design + +
+ +

Implementing Domain-Driven Design

+
+

by Vaughn Vernon

+

The Big Red Book provides practical guidance on how to apply DDD. Building on Eric + Evans’ seminal book, Domain-Driven Design, the author presents practical DDD techniques + through examples from familiar domains.

+
+
+ +
+ + Domain-Driven Design Distilled + +
+ +

Domain-Driven Design Distilled

+
+

by Vaughn Vernon

+

If you are new to Domain-Driven Design, this book and the Domain-Driven Design Reference + by Eric Evans are the best way to get into the topic.

+
+
+ +
+ + Introducing EventStorming + +
+ +

Introducing EventStorming

+

An act of deliberate collective learning

+
+

by Alberto Brandolini

+

The deepest tutorial and explanation about EventStorming, straight from the inventor. + It provides guidance on leveraging the potential of this collaborative brainstorming + technique used to identify domain events.

+
+
diff --git a/docs/resources/communities.md b/docs/resources/communities.md new file mode 100644 index 0000000..d3533f4 --- /dev/null +++ b/docs/resources/communities.md @@ -0,0 +1,26 @@ +--- +title: Communities +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +# Communities + +

Places to discuss DDD with other practitioners

+ +- [Virtual DDD](https://virtualddd.com/) (EN)
+A community-driven online meetup for people who want to get more in-depth knowledge of DDD +from anywhere at anytime. + +- [DDD/CQRS Google Group](https://groups.google.com/g/dddcqrs?pli=1) (EN)
+A mailing list with practical discussions on DDD, CQRS and Event Sourcing. + +- [Domain-Driven Design Injection](https://dddi.dev/) (RU)
+Our own community for developers and engineers who look for the new ways of solving non-trivial software design tasks. +[Regular meetups](https://dddi.dev/logbook) are held online. The discussion in between the meetups continues in the +[DDDi Messroom](https://messroom.dddi.dev/). + +- [DDDevotion](https://t.me/dddevotion) (RU)
+A community, dedicated to DDD, engineering practices, and technical excellence. + diff --git a/docs/resources/index.html b/docs/resources/index.html new file mode 100644 index 0000000..cace017 --- /dev/null +++ b/docs/resources/index.html @@ -0,0 +1,72 @@ +--- +title: DDD Resources +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +

Resources on Domain-Driven Design

+

A brief selection of learning materials we recommend to the colleagues in DDD

+ + + + + + diff --git a/docs/resources/libraries.md b/docs/resources/libraries.md new file mode 100644 index 0000000..fd2fa74 --- /dev/null +++ b/docs/resources/libraries.md @@ -0,0 +1,20 @@ +--- +title: Libraries and Frameworks +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +# Libraries and Frameworks + +

Other tools that help with DDD in code

+ +- [Axon](https://axoniq.io/). +Open source framework for event-driven microservices and domain-driven design. + +- [Vlingo](https://vlingo.io/). +The open source toolkit by Vaughn Vernon for fluent reactive, event-driven, +and microservices architectures. + +- [EventStore](https://eventstore.com/). +The stream database by Greg Young built for Event Sourcing. diff --git a/docs/resources/people.html b/docs/resources/people.html new file mode 100644 index 0000000..86d571f --- /dev/null +++ b/docs/resources/people.html @@ -0,0 +1,80 @@ +--- +title: People +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +

People

+

The creators and drivers of the DDD ideas

+ +
+ Eric Evans +
+

Eric Evans

+ +

The author of the DDD methodology. Domain linguist. The author + of “Domain-Driven Design: Tackling Complexity in Software”.

+
+
+ +
+ Vaughn Vernon +
+

Vaughn Vernon

+ +

Domain Model Whisperer. The author of “Implementing Domain-Driven Design” + and “Domain-Driven Design Distilled”. + Founder of Vlingo Platform.

+
+
+ +
+ Greg Young +
+

Greg Young

+ +

Creator of CQRS and the driver of Event Sourcing. + Founder of the Event Store.

+
+
+ +
+ Alberto Brandolini +
+

Alberto Brandolini

+ +

The inventor of #EventStorming. Practicing engineer and a coach. The author of + Introducing “EventStorming – An act of deliberate collective learning”.

+
+
+ +

And many more advancing the approach every day.

diff --git a/docs/resources/sites.md b/docs/resources/sites.md new file mode 100644 index 0000000..1daf474 --- /dev/null +++ b/docs/resources/sites.md @@ -0,0 +1,22 @@ +--- +title: Sites +headline: DDD Resources +bodyclass: docs resources +layout: docs +--- + +# Sites + +

Learning hubs

+ +- [DDD Community](https://dddcommunity.org/). +A collection of resources by Eric Evans and his colleagues. + +- [Domain Driven Design On InfoQ](https://www.infoq.com/domain-driven-design/). +DDD section on one of the leading media on software development. + +- [Awesome Domain-Driven Design](https://github.com/heynickc/awesome-ddd). +An extensive curated list of DDD, CQRS, Event Sourcing, and EventStorming resources. + +- [Learning DDD](https://emacsway.github.io/ru/self-learning-for-software-engineer/#ddd). +A selection of learning materials for gradual immersion into the topic: from the basics to the specific design cases.