Skip to content

Conversation

@MaxShoshin
Copy link

No description provided.

@MaxShoshin MaxShoshin marked this pull request as ready for review June 9, 2020 10:34
@MaxShoshin
Copy link
Author

MaxShoshin commented Jun 9, 2020

The base idea: after successfull publishing write information about range of published events (min version, max version, aggregateId).
Also it requires background process (in application, it is not implemented here) which call IPublishVerificator periodically to verify that committed to IEventPersistence events are successfully published. Log rows removed after successfull verification. To avoid compare all event store this process stores last verified GlobalPosition.

This implementation is EventPersistance agnostic.
Currently it uses MsSQL for log storage, but it can be easlily enhance for all SQL Like storages. Currently, I'm not sure about EntityFramework and Mongo storages...

This implementation is fully separate from existing code - so no breaking changes, etc.

@MaxShoshin
Copy link
Author

@leotsarev , could you review and discuss?

@MaxShoshin MaxShoshin requested a review from leotsarev June 9, 2020 14:02
@MaxShoshin MaxShoshin force-pushed the crash-resilience-poc branch from 6b201d5 to 5b58b52 Compare June 11, 2020 07:40
using EventFlow.Aggregates;
using EventFlow.ReadStores;

namespace EventFlow.PublishRecovery
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be WAY easier if we just can resolve IRecoveryHandler<TReadModel> from resolver


await _msSqlConnection.ExecuteAsync(
Label.Named("publishlog-commit"),
CancellationToken.None, // Unable to Cancel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we could not cancel here? If some callers do not have cancellation token, they could pass None

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you should want to cancel writing info about successfully published to side effects?

Copy link
Member

@leotsarev leotsarev Jun 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question goes for MarkVerifiedAsync (and basically every update), isn't it? Let's be consistent
I think "should I be allowed to cancel" is not for persistence to decide....

{
public interface IReadModelRecoveryHandler
{
Task RecoverFromShutdownAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we are inconsistent in signatures? I think it's ok to just have "if you throw, you failed to recover". I don't like API that's tell us "we could be fallible", because we want user to try to get infallible API ;-)

IReadOnlyCollection<IDomainEvent> eventsForRecovery,
CancellationToken cancellationToken);

Task<bool> RecoverFromErrorAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm highly in doubt that we really need to distinct methods. May be parameter like "RecoveryReason" will fit better, cause usually people will like to implemement both methods they same.


namespace EventFlow.PublishRecovery
{
public sealed class PublishVerificator : IPublishVerificator
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't reviewed this file cause I hope to do it later in separate review

{
public interface IReliableMarkProcessor
{
Task MarkEventsPublishedAsync(IReadOnlyCollection<IDomainEvent> domainEvents);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this abstraction actually now.
We now have only two reasons left for that:

  1. To inject a NOP implementation (it could be easily done by injecting NOP persistence)
  2. To group domain events by aggregate (see my persistence comment)

_msSqlConnection = msSqlConnection;
}

public async Task MarkPublishedAsync(IIdentity aggregateIdentity, IReadOnlyCollection<IDomainEvent> domainEvents)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that by taking aggregateIdentity parameter you are leaking your implementation into interface.
This could be determined from domainEvents and some other implementations could be fine with marking events from different aggregateIdentity in one roundtrip.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, I don't think it will be easy to completely remove this from interface.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's some kind of 'guarantee' that all domain events should related with one aggregate...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need this kind of guarantee?

var item = new PublishLogItem
{
AggregateId = aggregateIdentity.Value,
MinAggregateSequenceNumber = domainEvents.Min(x => x.AggregateSequenceNumber),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We got an assumption here that events are sequential. I thinks thats fine, but probably we should throw if assumption fails

logItems);
}

public async Task MarkVerifiedAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here — we are leaking implementation detail of having EventFlowPublishVerifyState into interface.
May be could just call MarkPublishedAsync from verificator and have some kind of Compact method which is implementation detail and could be MS SQL specific.

GlobalPosition newVerifiedPosition,
CancellationToken cancellationToken)
{
await _msSqlConnection.ExecuteAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm started to miss why no transaction here is okay. May be we need to
WHERE LastVerifiedPosition <@LastVerifiedPosition to be sure


namespace EventFlow.PublishRecovery
{
public interface IReliablePublishPersistence
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments in implementation about this interface.

{
public interface IReliablePublishPersistence
{
Task MarkPublishedAsync(IIdentity aggregateIdentity, IReadOnlyCollection<IDomainEvent> domainEvents);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we probably need to method to determine verification watermark for given aggregate for preventing publishing events out-of-order for aggregates. And yes, that's here where separate persistence for publish mark will add +1 read per command.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have to implement prevent events out of order right away (I'm rather not), but if we gonna publish separate PRs we need to include this in interface from first shot. It's thing for interfaces that you won't have 2nd chance to add another method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants