Skip to content
This repository was archived by the owner on Nov 6, 2023. It is now read-only.

Conversation

@rgomezcasas
Copy link
Member

@rgomezcasas rgomezcasas commented Jan 30, 2019

πŸ•΅οΈβ€β™‚οΈ In this PR you can find:

  • Some hexagonal architecture skeleton
  • A simple event bus using reactor
  • Some usage of that bus in the Starter

@rgomezcasas rgomezcasas self-assigned this Jan 30, 2019
@rgomezcasas rgomezcasas force-pushed the reactor branch 2 times, most recently from 66a3069 to d2ab4e4 Compare January 30, 2019 21:23
@rgomezcasas rgomezcasas changed the title Add a buggy simple implementation of an event bus using reactor Add a simple implementation of an event bus using reactor Jan 30, 2019
… available:

* `./gradlew run`: Run the main entrypoint (`Starter`) without having to reference it
* `./gradlew startScripts`: Creates OS specific scripts to run the project as a JVM application
* More info: https://docs.gradle.org/current/userguide/application_plugin.html
* Use immutable `Set` instead of `List` as the subscribers collection while instantiating the `ReactorEventBus`: We don't need to guarantee the subscribers order (in fact, we should avoid depending on it in order to avoid creating dependencies between subscribers based on execution order)
* Rename `VideoCreated` semantics by `VideoPublished` in order to bring more domain related language to the code instead of a CRUDy language
* Rename `DomainEvent#domainEventName` to `DomainEvent#fullQualifiedEventName`. This way we are being explicit about using a FQEN naming convention (which has been also introduced in this commit: `vendor.bounded_context.subdomain.[event|command|query].version.resource.event_occured`)
* Modify the `DomainEventSubscriber#subscribedTo` method return type from `String` to `Class<EventType extends DomainEvent>`. This way we gain a robuster contract avoiding possible mistakes returning any random string instead of the actual event we want to subscribe to.
    * This also has allowed us to not having to expose as public the constant with the event name and access it from the event bus implementation. That is, now we're having a really modullar implementation Open/Closed Principle compliant.
    * Set the FQEN constant as private and only expose as public the `DomainEvent#fullQualifiedEventName` method. This way we ensure by the `DomainEvent` interface that all subclasses will have the needed information in order to deal with them while serializing and so on (previously we were depending on the constant which isn't declared in the interface). I've left this `DomainEvent#fullQualifiedEventName` in order to use it when we publish the events to some distributed message broker.
* Renamed the `EventBus#notify` method to `EventBus#publish`, and `DomainEventSubscriber#react` to `DomainEventSubscriber#consume`. This way we're respecting the same semantics we use while talking about Domain Events (subscribing, publishing, and consuming) without being polluted by the semantics of Java Reactor (`react`)
* Refactor the `ReactorEventBus` class in order to use `Set#forEach` method and lambda functions extracted into their own methods in order to make it easier to read
…ass and execute it from the main entry point

* Create the `AggregateRoot` abstract class in order to deal with recorded domain events being able to pull them out afterwards
* Refactor the `EventBus#publish` contract in order to receive a `List` of `DomainEvent`s instead of a single one. I've tried with the varargs approach in order to be able to publish one single event or a list of them, but it wouldn't match the actual contract of the `AggregateRoot#pullDomainEvents(): List<DomainEvent>` return type. So taking into account we would have to convert it to an `Array` (cost O(n)) just because of that, I think it make sense to just use `List` on the `EventBus` side and simplify the signatures at least for now 🀟
* I've go for doing the conversion between primitive types to value objects inside the Application Service. We're used to to do so in the Command/Query handler, and when we don't have a command/query bus we tend to do so in the entrypoint controller. However, I think this way we avoid some boilerplate on the different entrypoints a use case might have, and we avoid coupling our infrastructure to all the internal structure of our domain. What do you think?
* Move `tv.codely.Starter` to `tv.codely.context.video.module.video.infrastructure.VideoPublisherCliController` in order to serve as entrypoint controller through CLI
@JavierCane
Copy link
Member

I would like to explain some of the decisions made in the pushed commits in order to ask for your opinion @rgomezcasas. Sorry for thinking I'm Cervantes πŸ˜…

  1. Add Application Gradle plugin in order to have the following commands available:
  • ./gradlew run: Run the main entrypoint (Starter) without having to reference it
  • ./gradlew startScripts: Creates OS specific scripts to run the project as a JVM application
  • More info: docs.gradle.org/current/userguide/application_plugin.html
  1. Refactor the EventBus example polishing out some details:
  • Use immutable Set instead of List as the subscribers collection while instantiating the ReactorEventBus: We don't need to guarantee the subscribers order (in fact, we should avoid depending on it in order to avoid creating dependencies between subscribers based on execution order)
  • Rename VideoCreated semantics by VideoPublished in order to bring more domain related language to the code instead of a CRUDy language
  • Rename DomainEvent#domainEventName to DomainEvent#fullQualifiedEventName. This way we are being explicit about using a FQEN naming convention (which has been also introduced in this commit: vendor.bounded_context.subdomain.[event|command|query].version.resource.event_occured)
  • Modify the DomainEventSubscriber#subscribedTo method return type from String to Class<EventType extends DomainEvent>. This way we gain a robuster contract avoiding possible mistakes returning any random string instead of the actual event we want to subscribe to.
    • This also has allowed us to not having to expose as public the constant with the event name and access it from the event bus implementation. That is, now we're having a really modullar implementation Open/Closed Principle compliant.
    • Set the FQEN constant as private and only expose as public the DomainEvent#fullQualifiedEventName method. This way we ensure by the DomainEvent interface that all subclasses will have the needed information in order to deal with them while serializing and so on (previously we were depending on the constant which isn't declared in the interface). I've left this DomainEvent#fullQualifiedEventName in order to use it when we publish the events to some distributed message broker.
  • Renamed the EventBus#notify method to EventBus#publish, and DomainEventSubscriber#react to DomainEventSubscriber#consume. This way we're respecting the same semantics we use while talking about Domain Events (subscribing, publishing, and consuming) without being polluted by the semantics of Java Reactor (react)
  • Refactor the ReactorEventBus class in order to use Set#forEach method and lambda functions extracted into their own methods in order to make it easier to read
  1. Extract the use case business logic to its own Application Service class and execute it from the main entry point
  • Create the AggregateRoot abstract class in order to deal with recorded domain events being able to pull them out afterwards
  • Refactor the EventBus#publish contract in order to receive a List of DomainEvents instead of a single one. I've tried with the varargs approach in order to be able to publish one single event or a list of them, but it wouldn't match the actual contract of the AggregateRoot#pullDomainEvents(): List<DomainEvent> return type. So taking into account we would have to convert it to an Array (cost O(n)) just because of that, I think it make sense to just use List on the EventBus side and simplify the signatures at least for now 🀟
  1. Add VideoTitle and VideoDescription Value Objects examples:
  • I've go for doing the conversion between primitive types to value objects inside the Application Service. We're used to to do so in the Command/Query handler, and when we don't have a command/query bus we tend to do so in the entrypoint controller. However, I think this way we avoid some boilerplate on the different entrypoints a use case might have, and we avoid coupling our infrastructure to all the internal structure of our domain. What do you think?
  • Move tv.codely.Starter to tv.codely.context.video.module.video.infrastructure.VideoPublisherCliController in order to serve as entrypoint controller through CLI
  1. Add the unit test for the VideoPublisher

@JavierCane JavierCane merged commit 1803875 into master Jan 31, 2019
@JavierCane JavierCane deleted the reactor branch January 31, 2019 03:26
@JavierCane JavierCane self-assigned this Jan 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants