Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventStore repository to allow connection factory to be passed #107

Closed
wants to merge 7 commits into from

Conversation

ninjaboy
Copy link
Contributor

@ninjaboy ninjaboy commented Oct 9, 2018

This change introduces connection factory method as a dependency to the EventStore repository instead of connection object to allow more flexibility with regards to how connection object is resolved. (e.g. to allow connection polling, re-connection and monitoring)

Work done

  1. Major changes

Changed EventStoreRepository constructor from
public EventStoreRepository(IHoldAllConfiguration configs, IEventStoreConnection connection)
to
public EventStoreRepository(IHoldAllConfiguration configs, Func<IEventStoreConnection> connectionFactory)

  1. Important things to review

EventStoreRepository as the method to create new session was changed

Integrity

  • I have had a look on the PR preview for obvious whitespace\formatting issues before actually openned this PR
  • I have run unit tests
  • I have run EventStore integration tests

…y instead of connection objectct to allow more flexibility with regards to how connection object is resolved. (e.g. to allow connection polling, reconnection and more monitoring)

if (connection == null)
{
throw new RepositoryUnavailableException("Couldn't connect to the EvenStore repository. See InnerException for details", new ArgumentNullException(nameof(connection)));
Copy link
Collaborator

Choose a reason for hiding this comment

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

ArgumentNullException is not appropriate here - connection is a result, not an argument.
InvalidOperationException maybe, or do not passing anything as InnerException if there is no exception

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Valid point

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed InnerException at all

Copy link
Collaborator

@iblazhko iblazhko left a comment

Choose a reason for hiding this comment

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

I think there is a problem with connection lifecycle.
Connection is created by calling the factory in the EventstoreRepository, but the connection is never disposed of explicitly.

I propose to pass factory into EventStoreSession, create new instance of the connection in the EventStoreSession constructor, and dispose the connection in the session's Dispose:

public class EventStoreSession<TState> : BaseEventSourcedSession<TState, int>
{
...
        public EventStoreSession(IHoldAllConfiguration configuration,
                                 Func<IEventStoreConnection> eventStoreConnectionFactory,
                                 string streamName)
            : base(configuration)
        {
            this.eventStoreConnection = eventStoreConnectionFactory(); // check for null factory before that, move error handling from repository here
            this.streamName = streamName ?? throw new ArgumentNullException(nameof(streamName));
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ConsiderSessionDisposed();
                eventStoreConnection.Dispose();
            }
            base.Dispose(disposing);
        }
}

@ninjaboy
Copy link
Contributor Author

ninjaboy commented Oct 9, 2018

@iblazhko , thanks for the spot Ivan, I have addressed your comment

throw new StreamNotFoundException(id.ToString());
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

throw at L40 will leave the connection open.


return session;
}

public async Task<bool> Contains(TId selector)
private void CleanupConnection(IEventStoreConnection connection)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This cleanup function could potentially be shared between EventStoreRepository and EventStoreSession

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Member

@skleanthous skleanthous left a comment

Choose a reason for hiding this comment

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

@iblazhko & @ninjaboy I am a bit worried about this API as it is now. The reason is that DI frameworks like Autofac will emit a Func for you even if you register a connection as single instance (see https://autofaccn.readthedocs.io/en/latest/advanced/delegate-factories.html). In this case if an instance is disposed, then any subsequent calls to the factory method will return a disposed version of the connection.

What I think would be best is to implement a factory of our own that explicitly manages instantiation of the connection and its lifetime.

@ninjaboy
Copy link
Contributor Author

@skleanthous , Shall explore the following approach then:

EventStoreRepository repository; //injected via ctor
void Operation(string id){
   var connection = GetConnection(); //client decides whether it's from pool or a new one or whatever
   using(repository.BeginSessionForId(connection, id)){
       //do stuff...
   }
   //now I decide what to do with the connection
   connection.Close();
}

@skleanthous
Copy link
Member

@ninjaboy Something like connectionFactory.GetSingleInstance() vs connectionFactory.GetTransient()? If so then yeah, that's perfect.

@ninjaboy
Copy link
Contributor Author

@skleanthous , GetConnection() -> This is up for a client to decide. They may choose whatever strategy to manage the connection (e.g. reuse between sessions, or open close or whatever, the session only requires the connection to be open.

However, looking at the interface that repository currently implements I won't be able to pass arbitrary parameters to the session. Because I have to stick to:

    public interface IStartSessions<in TEntitySelector, TState>
    {
        Task<IManageSessionOf<TState>> BeginSessionFor(TEntitySelector selector, bool throwIfNotExists);
        Task Delete(TEntitySelector selector);
        Task<bool> Contains(TEntitySelector selector);
    }

@ninjaboy ninjaboy closed this Oct 10, 2018
@ninjaboy ninjaboy reopened this Oct 10, 2018
@ninjaboy
Copy link
Contributor Author

I must say that I am really confused so far with how the issue needs to be resolved.
Here are some links that I am trying to follow to figure out the possible solution:
EventStore/EventStore#1584
EventStore/EventStore#1732
https://groups.google.com/forum/#!topic/event-store/nkgzXpy56NQ
https://groups.google.com/forum/#!topic/event-store/xu1GQzZtCiM
https://groups.google.com/forum/#!topic/event-store/z0RWBnBesoU

Any suggestion on how we shall handle the problem with connection object going into the Disposed state are welcome.

@ninjaboy
Copy link
Contributor Author

https://groups.google.com/forum/#!topic/event-store/nkgzXpy56NQ
This post is particularly helpful and in the end it mentions the change introduced in v4.1.1 that introduces a function SetMaxQueueTimeout that looks ismilar to what we need. However .net core version of event store client exists only for version 4.1.0.23 and the new call is introduced in v 4.1.1.

@ninjaboy
Copy link
Contributor Author

BTW, I tried EventStore.Client.NetCore.4.1.1-rc and SetMaxQueueTimeout but it still doesn't work as expected with KeepReconnecting

@skleanthous
Copy link
Member

Will be done in a future release

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.

None yet

3 participants