Giter Site home page Giter Site logo

vaughnvernon / iddd_samples Goto Github PK

View Code? Open in Web Editor NEW
3.7K 246.0 893.0 546 KB

These are the sample Bounded Contexts from the book "Implementing Domain-Driven Design" by Vaughn Vernon: http://vaughnvernon.co/?page_id=168

License: Other

Java 99.70% Shell 0.24% Batchfile 0.05%

iddd_samples's Introduction

These are the sample Bounded Contexts from the book "Implementing Domain-Driven Design" by Vaughn Vernon:

http://vaughnvernon.co/?page_id=168

The models and surrounding architectural mechanisms may be in various states of flux as the are refined over time. Some tests may be incomplete. The code is not meant to be a reflection of a production quality work, but rather as a set of reference projects for the book.

Points of Interest

The iddd_agilepm project uses a key-value store as its underlying persistence mechanism, and in particular is LevelDB. Actually the LevelDB in use is a pure Java implementation: https://github.com/dain/leveldb

Currently iddd_agilepm doesn't employ a container of any kind (such as Spring).

The iddd_collaboration project uses Event Sourcing and CQRS. It purposely avoids the use of an object-relational mapper, showing that a simple JDBC-based query engine and DTO matter can be used instead. This technique does have its limitations, but it is meant to be small and fast and require no configuration or annotations. It is not meant to be perfect.

It may be helpful to make one additional mental note on the iddd_collaboration CQRS implementation. To keep the example simple it persists the Event Sourced write model and the CQRS read model in one thread. Since two different stores are used--LevelDB for the Event Journal and MySQL for the read model--there may be very slight opportunities for inconsistency, but not much. The idea was to keep the two models as close to consistent as possible without using the same data storage (and transaction) for both. Two different storage mechanisms were used purposely to demonstrate that they can be separate.

The iddd_identityaccess project uses object-relational mapping (Hibernate), but so as not to leave it "boring" it provides a RESTful client interface and even publishes Domain-Event notifications via REST (logs) and RabbitMQ.

Finally the iddd_common project provides a number of reusable components. This is not an attempt to be a framework, but just leverages reuse to the degree that code copying doesn't liter each project. This is not a recommendation, but it did work well and save a considerable amount of work while producing the samples.

Usage

Requires

  • Java 7 (8+ does not work)
  • MySQL Client + Server
  • RabbitMQ

Setup (with Docker)

To make it easy to run the tests and it requirements, the startContainers.sh script is provided. Which will start a:

  • MySQL Server container
  • RabbitMQ Server container
  • RabbitMQ Management container

If the mysql command is available, which is the mysql client, also the required SQL scripts will be imported into the MySQL Server.

If you use the startContainers.sh script, you don't need MySQL Server and RabbitMQ installed locally. Instead, Docker needs to be installed as the script will start MySQL and RabbitMQ in Docker containers.

Build

You can build the project by running:

./gradlew build

This automatically downloads Gradle and builds the project, including running the tests.

The Gradle build using Maven repositories was provided by Michael Andrews (Github michaelajr and Twitter @MichaelAJr). Thanks much!

I hope you benefit from the samples.

Vaughn Vernon Author: Implementing Domain-Driven Design Twitter: @VaughnVernon http://vaughnvernon.co/

iddd_samples's People

Contributors

carlosbuenosvinos avatar chytonpide avatar geraldcroes avatar pvdissel avatar vaughnvernon avatar vlingo-java avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

iddd_samples's Issues

Store aggregates: mongodb or postgres json

Hello, Mr VernonI.

I read the article "The Ideal Domain-Driven Design Aggregate Store?" at https://kalele.io/the-ideal-domain-driven-design-aggregate-store/
Now mongodb already supports ACID transactions (after 2018 summer).I wonder whether mongodb is capable of storing ddd in production environment or not.

I'm worried about performance.

https://docs.mongodb.com/master/core/transactions/ :

IMPORTANT
In most cases, multi-document transaction incurs a greater performance cost over single document writes, and the availability of multi-document transactions should not be a replacement for effective schema design.

https://www.mongodb.com/blog/post/multi-document-transactions

With subdocuments and arrays, document databases allow related data to be unified hierarchically inside a single data structure. The document can be updated with an atomic operation, giving it the same data integrity guarantees as a multi-table transaction in a relational database.

Because of this fundamental difference in data modeling, MongoDB’s existing atomicity guarantees are able to meet the data integrity needs of most applications. In fact, we estimate 80%-90% of applications don’t need multi-document transactions at all.

As you can see in this two articles written by mongodb official, the ACID transaction of mongodb is probably designed for some corner cases (probably 10% of 10% projects, I guess). But in DDD we use multi-document transactions frequently. ACID transaction is needed whenever we want to publish or handle a domain event.

So I'm worried that the way they design mongodb makes the transaction of mongodb not friendly with Domain Driven Design. But I cannot find benchmarks or some other articles about this topic. Would you please answer my question?

In addition, in https://kalele.io/the-ideal-domain-driven-design-aggregate-store/ , you said that postgres is very fast. I would like to know which one should be chosen to store aggregates (1) when db runs on single machine (2) when db is distributed

Why do some contexts contain port & adapter modules and others don't?

I have a doubt about the code organization. As an example, the com.saasovation.identityaccess.infrastructure.persistence package contains classes that implement repositories using hibernate. But a similar purpose is served by the com.saasovation.agilepm.port.adapter.persistence package in the other context which was implemented with LevelDB.

One possible explanation that I see is that hibernate is considered the adapter and that's why there is no need for implementing a package com.saasovation.identityaccess.port.adapter.persistence. However, in the agilepm context, there is no infrastructure layer at all and only the port & adapter layer. Is that because, for example, com.saasovation.agilepm.port.adapter.persistence.LevelDBSprintRepository fulfills the role of infrastructure and adapter at the same time and you chose to to not separate those concerns?

Another doubt is that the endpoints declared in the com.saasovation.identityaccess.resource package are at the "top level" of that context. I would have expected them to be in port & adapters.

Thank you for any help to clear up my confusion.

Constructor vs Static Factory Method

Hi Vaughn.

First of all, thank u for contributions in computer science world.

While I was reading ur example, I asked myself about the domain models. I think that constructors have a lot of responsabilities, since they call set methods in which it validates and throw exceptions if it violates any domain rule. Why do u use constructors and not another pattern? Maybe a SFM (static factory method) or Builder can help to keep constructors simpler.

Thanks in advance.

The setup script fails

Starting rabbitMQ container fails on Mac because of the invalid node name.

startContainers.sh use hostname as RABBITMQ_NODENAME.

rabbitmqNodeName="$(hostname)"
...
docker run --name "${rabbitmqContainerName}" -p 5672:5672 -p "${rabbitmqManagementHttpPort}":15672 
-e RABBITMQ_NODENAME="${rabbitmqNodeName}" -d rabbitmq:3-management

Dots are not available as RABBITMQ_NODENAME.
but, on Mac, $(hostname) is like "chytonpide_mac.local" as a default.
(There is a very high probability that the hostname contains a dot, even if it is a different system.)

I think it is unnecessary to use the "RABBITMQ_NODENAME" environment variable.
So I suggest deleting the "RABBITMQ_NODENAME" environment variable on the line that runs the RabbitMQ container or using a static node name that does not include dots.

SQLs execution fails after starting MySQL container because MySQL server is not ready.

As soon as the container is ready, SQLs are executed. But, executions fail because the Mysql server is not prepared even if the container is ready.

I suggest having a delay after the container is ready.

waitForContainer "${mysqlContainerName}" "mysqld: ready for connections."

sleep 30

Not the best solution, but it works and is simple :)

Using docker-compose

I suggest replacing the setup script with docker-compose.
I think it makes the setup process easier to maintain. it also could be used in Windows systems.
The docker-compose is included in Docker for Mac and Docker for Windows installer.
So there is no need for other installation processes for this.

Tests fail due to LevelDB path not present

When I run gradle clean build test cases fail with:

java.lang.IllegalStateException: Cannot open LevelDB database: /data/leveldb/iddd_agilepm_db because: Database directory '/data/leveldb/iddd_agilepm_db' does not exist and could not be created
    at com.saasovation.common.port.adapter.persistence.leveldb.LevelDBProvider.openDatabase(LevelDBProvider.java:140)

Question regarding concurrencyVersion

Hello,

there is a setter for concurrencyVersion in ConcurrencySafeEntity. But this setter is not called from anywhere.
How is it supposed to be used? Shouldn't be the concurrencyVersion contained in every command send to the entity to submit the version the client has seen when reading the aggregat in the first place?
If so, what would be the best place to call setConcurrencyVersion? In the application service (or even in the repository) after loading the aggregate or in the entities' methods?

Thanks + Best Regards,
Stefan.

database schema of agilepm

Hi Vaughn,

I've been searching for the database schema of agilepm. The other context have .sql files.
Where can I find it?

Post on a closed discussion or closed forum

Hello,

According to the code, it's possible to post on a closed discussion. I am missing something ?
We can easily fix it by adding an assertion on Discussion.post would fix the problem.

But what about posting on a opened discussion that belongs to a closed forum ?
Does moving post method from Discussion to Forum be a good solution ?

Thank you

What's the point of com.saasovation.identityaccess.application.command?

What's the point of having different packages com.saasovation.identityaccess.application and com.saasovation.identityaccess.application.command?

  • If you use AssignUserToRoleCommand, you use AccessApplicationService. If you use AccessApplicationService, you use AssignUserToRoleCommand. If classes are used together, it makes sense for them to be in the same package. The Common Reuse Principle: "Classes that are used together are packaged together". See http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod.
  • Inside com.saasovation.identityaccess.domain.model services and other types of classes (entities, ...) are in the same packages. Why should it be different inside com.saasovation.identityaccess.application?

Build fails because of the version of tools

Couldn't download libraries when building project with Gradle v2.3 and Java7

Maven central discontinued support for TLSv1.1 and below.
Gradle v2.3 with Java7 used in the project use TLSv1.1
so it is needed to use Gradle version 4.8.1 and above or Java8 and above that supports TLSv1.2.

Available options

  • Use gradlew version 4.8.1 and above.(from v4.8.1 to 6.9.2 available. on v7.0 and above, the build script needs to be changed.)
  • Use Java8.
    • The version of spring needs to be changed to v3.2.18.

mysql-connector-java v5.1.6 is not compatible with MySQL latest image(v8.0.28) on docker

It is needed to use a compatible version of MySQL with mysql-connector-java.

Available options

  • Use MySQL v5.7; specify the version of MySQL on the line that runs the docker container in the setup script.
  • Use MySQL latest image on docker with mysql-connector-java v8.0.28.
    • The column data type on collaboration.sql needs to be changed from varchar(65000) to text or specify charset as latin1 when creating DB because MySQL v8.0.28 default database charset is utf8mb4.
    • AbstractQueryService needs to be changed to use ResultSet.TYPE_SCROLL_SENSITIVE as resultSetType.

Summary

Every combination of available options passed all tests except two SlotMQ tests.
The combination of using Java7, MySQL v5.7, and Gradle v4.8.1 is the minimum change.
Using Java8 needs to change the Spring dependency of the version to v3.2.18.
Using MySQL v8.0.28 needs to change "collaboration.sql" and AbscractQueryService.

Erroneous log id when event store is empty

The method c.s.common.notification.NotificationLogFactory.calculateCurrentNotificationLogId computes an erroneous NotificationLogId when the event store is empty. For example NOTIFICATIONS_PER_LOGis set to 20. Then at line 61, 0 % 20 = 0. Then at line 64 remainderis set to 20. Finally, at line 67, lowis set to 0 - 20 + 1 = 19.

By changing the condition at line 63 into remainder == 0 && count > 0, the issue is fixed.

Where is functions that retrieves data to display

Sorry for the dump question. When I read the code in iddd_agilepm module, I don't find out any services to retrieve data to display. I guess that it is CQRS and just show only command (update database), so it ignores query model.

so, it may have two options:

  1. Build dedicated query model

  2. Add query functions in the services. If do that, I found problem of small aggregates. Because of separating big grasp of object by join sql query into small ones with multiple single-table sql query that is more slower.

thanks for your reading, hope you can answer me

why some entities in equals(..) use many properties and not only their ID?

Hi

Vaughn, could you explain it? I'd appreciate ;]
For example Product: uses ProductId and TentatId.

public class Product extends Entity {

    private Set<ProductBacklogItem> backlogItems;
    private String description;
    private ProductDiscussion discussion;
    private String discussionInitiationId;
    private String name;
    private ProductId productId;
    private ProductOwnerId productOwnerId;
    private TenantId tenantId;

    // ..

    @Override
    public boolean equals(Object anObject) {
        boolean equalObjects = false;

        if (anObject != null && this.getClass() == anObject.getClass()) {
            Product typedObject = (Product) anObject;
            equalObjects =
                this.tenantId().equals(typedObject.tenantId()) &&
                this.productId().equals(typedObject.productId());
        }

        return equalObjects;
    }
}

Entity is identified by its ID (ProductId) thus:

  • shouldn't ProductId be the only property used in equals(..)?
  • isn't using more properties in equals(..) breaking rule that entities are identified (compared, matched) only by ID?

Thank you
GT

BTW. Great book. I'm reading it 2nd time ;]

[question] implicit dependency from iddd_agilepm to iddd_identityaccess

The class com.saasovation.agilepm.port.adapter.messaging.rabbitmq.RabbitMQTeamMemberEmailAddressChangedListener is listening to the event com.saasovation.identityaccess.domain.model.identity.PersonContactInformationChanged which is fired from the iddd_identityaccess module.

I think it's an implicit dependency between the two modules that actually make this two modules coupled

I am not sure if there are any better approach to eliminate this kind of implicit dependency

Updating multiple aggregates in the same transaction?

Inside ProductApplicationService there appears to be a transaction boundary, culminated with `ApplicationServiceLifeCycle.success().

It wraps calls to multiple repositories, which, to my understanding, saves changes made in two distinct aggregate roots:

Product product =
this.productRepository()
.productOfId(
new TenantId(aCommand.getTenantId()),
new ProductId(aCommand.getProductId()));
if (product == null) {
throw new IllegalStateException(
"Unknown product of tenant id: "
+ aCommand.getTenantId()
+ " and product id: "
+ aCommand.getProductId());
}
product.initiateDiscussion(new DiscussionDescriptor(aCommand.getDiscussionId()));
this.productRepository().save(product);
ProcessId processId = ProcessId.existingProcessId(product.discussionInitiationId());
TimeConstrainedProcessTracker tracker =
this.processTrackerRepository()
.trackerOfProcessId(aCommand.getTenantId(), processId);
tracker.completed();
this.processTrackerRepository().save(tracker);
ApplicationServiceLifeCycle.success();

I want to make sure that I'm not missing any important context here. As per Vernon's own words:

A properly designed aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction. And a properly designed bounded context modifies only one aggregate instance per transaction in all cases.

Is this an example of a situation where the rule of thumb does not apply? Does anyone have a better explanation?

State in domain services

Hi,

In the book, it's clearly stated that domain services should be stateless. However, we noticed that BusinessPriorityCalculator (and possibly other services) do store a repository reference.

We can see why this may not be an issue in practice, given that BacklogItemRepository should be a singleton, but we don't know if that's the reason why this exception was made.

What rules of thumb do you use when sidestepping the "services should be stateless" rule? Thanks!

How to use ValidationNotificationHandler

Hi,
while reading the book i stumbled around ValidationNotificationHandler.
Something that is missing both book and repo is the actual implementation of ValidationNotificationHandler.
What should be done inside handleError(String aNotificationMessage) ?

Thank you all.

sprint.commit vs. backlogItem.commitTo

I'm trying to understand the eventing and am a bit puzzled cause when I follow the invocations starting from RabbitMQBacklogItemCommitedListener, it calls SprintApplicationService, which calls sprint.commit(BacklogItem bli). It doesn't publish any event.

Then on the BacklogItem entity I also see a "commitTo" method, which does publish an event, but this method is only called from the tests.

That looks like something is wrong/inconsistent here...

Getting Peer not authenticated error when I'm trying to run gradlew build

hi,

I'm trying to compile this (using Java 7), using the gradlew build command. it ends up with peer not authenticated errors:

...
:iddd_common:compileJava`

FAILURE: Build failed with an exception.

* What went wrong:
Could not resolve all dependencies for configuration ':iddd_common:compile'.
> Could not resolve org.slf4j:slf4j-api:1.5.8.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.5.8/slf4j-api-1.5.8.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/org/slf4j/slf4j-api/1.5.8/slf4j-api-1.5.8.pom'.
      > peer not authenticated
> Could not resolve commons-logging:commons-logging:1.1.2.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/commons-logging/commons-logging/1.1.2/commons-logging-1.1.2.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/commons-logging/commons-logging/1.1.2/commons-logging-1.1.2.pom'.
      > peer not authenticated
> Could not resolve com.google.code.gson:gson:2.1.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/com/google/code/gson/gson/2.1/gson-2.1.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/com/google/code/gson/gson/2.1/gson-2.1.pom'.
      > peer not authenticated
> Could not resolve com.rabbitmq:amqp-client:3.0.4.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/com/rabbitmq/amqp-client/3.0.4/amqp-client-3.0.4.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/com/rabbitmq/amqp-client/3.0.4/amqp-client-3.0.4.pom'.
      > peer not authenticated
> Could not resolve org.hibernate:hibernate:3.2.7.ga.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/org/hibernate/hibernate/3.2.7.ga/hibernate-3.2.7.ga.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/org/hibernate/hibernate/3.2.7.ga/hibernate-3.2.7.ga.pom'.
      > peer not authenticated
> Could not resolve org.springframework:spring:2.5.6.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/org/springframework/spring/2.5.6/spring-2.5.6.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/org/springframework/spring/2.5.6/spring-2.5.6.pom'.
      > peer not authenticated
> Could not resolve org.iq80.leveldb:leveldb:0.5.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/org/iq80/leveldb/leveldb/0.5/leveldb-0.5.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/org/iq80/leveldb/leveldb/0.5/leveldb-0.5.pom'.
      > peer not authenticated
> Could not resolve org.aspectj:aspectjweaver:1.7.2.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/org/aspectj/aspectjweaver/1.7.2/aspectjweaver-1.7.2.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/org/aspectj/aspectjweaver/1.7.2/aspectjweaver-1.7.2.pom'.
      > peer not authenticated
> Could not resolve javassist:javassist:3.8.0.GA.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/javassist/javassist/3.8.0.GA/javassist-3.8.0.GA.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/javassist/javassist/3.8.0.GA/javassist-3.8.0.GA.pom'.
      > peer not authenticated
> Could not resolve javax.transaction:jta:1.1.
  Required by:
      IDDD_Samples:iddd_common:unspecified
   > Could not GET 'https://repo1.maven.org/maven2/javax/transaction/jta/1.1/jta-1.1.pom'.
      > peer not authenticated
   > Could not GET 'https://repository.jboss.org/nexus/content/groups/public-jboss/javax/transaction/jta/1.1/jta-1.1.pom'.
      > peer not authenticated

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

i've tried to inject "-Dmaven.wagon.http.ssl.insecure=true" and "-Dmaven.wagon.http.ssl.allowall=true" into the Java command itself but to no effect unfortunately. would you have any recommendation on how to go about fixing this?

tks.

I'm running Windows 10, latest build as of 10/31, and java version

java version "1.7.0_80" 
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)\
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

A duplicated tracker is created when retrying ProductDiscussionRequest

A duplicated tracker is created when retrying ProductDiscussionRequest.

  • ProductApplicationService.retryProductDiscussionRequest() calls Product.requestDiscussion().
  • Product.requestDiscussion() publish ProductDiscussionRequested event.
  • the listener calls ProductApplicationService.startDiscussionInitiation().

and then, the ProductApplicationService.startDiscussionInitiation() creates the tracker again even though a retry has been requested by an existing tracker.
I think it is needed to add the logic that creating a tracker only when the Product doesn't have discussionInitiation.

public void startDiscussionInitiation(StartDiscussionInitiationCommand aCommand) {
  ...
  if(product.discussionInitiationId() == null) {
    ...create new tracker
  }
  ...
}

Wrong computation of notification logs

I have discovered an insidious bug in NotificationLogFactory.calculateCurrentNotificationLogId.

Indeed, for this to work, we assume that stored event ids are generated by the database and that they is never any gap between them. However this is not a valid assumption as indicated in the MySQL documentation:

In all lock modes (0, 1, and 2), if a transaction that generated auto-increment values rolls back, those auto-increment values are “lost.” Once a value is generated for an auto-increment column, it cannot be rolled back, whether or not the “INSERT-like” statement is completed, and whether or not the containing transaction is rolled back. Such lost values are not reused. Thus, there may be gaps in the values stored in an AUTO_INCREMENT column of a table.

Consider this example: we have 20 stored events, the first identified by 1 and the last by 21. Because at some point we had to rollback, the generated id 3 is lost. So the stored events have the following ids: 1, 2, 4, 5,... up to 21. Also we have NOTIFICATIONS_PER_LOG = 20.

Now in the calculateCurrentNotificationLogId method the variables take the following values:

count = 20
remainder = count % NOTIFICATIONS_PER_LOG = 0

// since remainder == 0 && count > 0
remainder = NOTIFICATIONS_PER_LOG = 20

low = count - remainder + 1 =  20 - 20 + 1 = 1
high = low + NOTIFICATIONS_PER_LOG - 1 = 20

So the current notificationLogId is [1-20], which will return the stored events between ids 1 and 20. So this log will only contain 19 notifications and the last stored event will be ignored.

I have found two solutions to fix this problem:

  1. Add the lastStoredEventId() method to EventStore and replace the calls to countStoredEvents() with lastStoredEventId().

This way we don't use the number of stored events to calculate the current notification log and we don't skip any stored event anymore. However we have to accept that not all archived notification logs will contain exactly NOTIFICATIONS_PER_LOG notifications.

Note that in some extreme cases (with many rollbacks), we may even have empty notification logs. But as long as we don't rely on the exact number of notifications per log when we consume them, I think this solution is fine. This is what I have implemented in my company.

  1. Still use the countStoredEvents() method, but replace the allStoredEventsBetween(long aLowStoredEventId, long aHighStoredEventId) by something like allStoredEventsBetween(long aLowIndex, long aHighIndex) or allStoredEvents(long anOffset, long aLimit).

This way we don't rely on the stored event ids and we have no gap between the notification ids, i.e., the archived notification logs will always contain NOTIFICATIONS_PER_LOG notifications.

However for any given notification, its id will not always match the id of its encapsulated event. On one hand we can argue that this is fine, since it doesn't expose how stored events are identified internally. On the other hand, for debugging or other purposes, we may like the fact that a notification id reflects the stored event id.

So what do you think about that?

Authorisation and request genuinity checks

I have already read some articles about the this topic including this one from @VaughnVernon and I'm almost done with the book IDDD but I'm still not clear on some facts so sorry to bring it up here again.

To sum up what I already know I just want to go throw the facts here. It is clear that in almost all the cases, authorization is an integrated part of the domain model. For example only an author is allowed to post to a forum. It is also clear that authentication is NOT part of any bounded context but the ones that are directly related to this matter (i.e. identity and access here).

However there are some edge cases that still keep me wondering if there is an answer to them in DDD style to them or do they need a combination of other architecture with DDD.

Lets take the tenant provisioning as an example. How do we answer the question "who is authorised to provision a tenant?". Shall the actor here be part of scenario (like author in forum example) or is there a God-mode way that something happens without an actor involved? if so how we authorise it?

Or take the use case where "a moderator is allowed to modify a post on behalf of its author?". Is this a use case that we still need to model in current code? or is this the same use case and same service call where author himself modifies the post only with an extra check that if the request is done by a moderator who has proper role?

Another question is how do we verify the genuinity of the requester. We can't only depend on tenant and author's id as these could be part of public data that we freely show to everybody. For example as a link to author's profile on a forum post. So we need a secret (like password) to do the verification. Will this secret be part of the command sent to relevant application service? or shall it be done before the request reaches the application service? like inside JAXRS resource by calling directly the UserRepository.

Again sorry to bother but these are the things I can't just shake off my head.

gradle build fails on MacBook (cannot find symbol)

Hi,

We are preparing for the IDDD_Workshop in Paris and are getting the following error when running gradle build. We are not so familiar with Java (understatement), so I have no clue if this has to do with our Java version, or some other settings.

I am working on a MacBook Air with Java 1.6.0_65. We used brew install maven and brew install gradle to install gradle on our machines, so I suppose we have the latest version. Anyone a clue?

.../IDDD_Samples/iddd_common/src/main/java/com/saasovation/common/port/adapter/messaging/slothmq/SlothWorker.java:152: cannot find symbol symbol : method bind(java.net.InetSocketAddress) location: class java.nio.channels.ServerSocketChannel this.socket.bind(new InetSocketAddress(discoveryPort)); ^ .../IDDD_Samples/iddd_common/src/main/java/com/saasovation/common/port/adapter/messaging/slothmq/SlothWorker.java:191: cannot find symbol symbol : method bind(java.net.InetSocketAddress) location: class java.nio.channels.ServerSocketChannel this.socket.bind(new InetSocketAddress(HUB_PORT)); ^ .../IDDD_Samples/iddd_common/src/main/java/com/saasovation/common/port/adapter/persistence/ResultSetObjectMapper.java:227: cannot find symbol symbol : method isAlphabetic(char) location: class java.lang.Character if (Character.isAlphabetic(ch) && Character.isUpperCase(ch)) { ^ 3 errors :iddd_common:compileJava FAILED

Reference for functional programming

How different would be to change this reference project towards one embracing functional programming? Does it make sense rich domain model and event publishing from domain if we use extensively streams from Java or kotlin?

Can't enable Mysql in EventStoreProvider

I tried setting FOR_MYSQL to true and FOR_LEVELDB to false but the test would not proceed because of null exception.

If you trace these lines:

The instance is not initialized.

I tried putting mysqlJdbcEventStore before followStoreEventDispatcher in applicationContext-collaboration.xml so that the mysql is initialized first but it's not working also.

I am not a java guy so I dont actually know what's happening.

I would like to try MySQL instead of LevelDB.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.