Skip to content

Commit

Permalink
Merge pull request #163 from dolittle/aggregate-root-di
Browse files Browse the repository at this point in the history
Enable dependency injection for aggregate roots
  • Loading branch information
woksin authored Oct 26, 2022
2 parents 396e568 + 520f105 commit 557e671
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 76 deletions.
16 changes: 12 additions & 4 deletions Samples/Tutorials/Aggregates/Kitchen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,30 @@
using System;
using Dolittle.SDK.Aggregates;
using Dolittle.SDK.Events;
using Dolittle.SDK.Tenancy;
using Microsoft.Extensions.Logging;

[AggregateRoot("01ad9a9f-711f-47a8-8549-43320f782a1e")]
public class Kitchen : AggregateRoot
{
readonly EventSourceId _eventSource;
readonly ILogger<Kitchen> _logger;
int _ingredients = 2;

public Kitchen(EventSourceId eventSource)
: base(eventSource)
public Kitchen(EventSourceId eventSource, ILogger<Kitchen> logger)
{
_eventSource = eventSource;
_logger = logger;
}

public void PrepareDish(string dish, string chef)
{
if (_ingredients <= 0) throw new Exception("We have run out of ingredients, sorry!");
if (_ingredients <= 0)
{
throw new Exception("We have run out of ingredients, sorry!");
}
Apply(new DishPrepared(dish, chef));
Console.WriteLine($"Kitchen {EventSourceId} prepared a {dish}, there are {_ingredients} ingredients left.");
_logger.LogInformation("Kitchen {EventSourceId} prepared a {Dish}, there are {Ingredients} ingredients left", _eventSource, dish, _ingredients);
}

void On(DishPrepared @event)
Expand Down
2 changes: 2 additions & 0 deletions Samples/Tutorials/Aggregates/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Sample code for the tutorial at https://dolittle.io/tutorials/getting-started/csharp/

using System;
using Dolittle.SDK;
using Dolittle.SDK.Tenancy;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = Host.CreateDefaultBuilder()
Expand Down
28 changes: 26 additions & 2 deletions Source/Aggregates/AggregateRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Dolittle.SDK.Aggregates.Internal;
using Dolittle.SDK.Artifacts;
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Store;
Expand All @@ -17,14 +18,22 @@ namespace Dolittle.SDK.Aggregates;
public abstract class AggregateRoot
{
readonly List<AppliedEvent> _appliedEvents = new();

EventSourceId? _eventSourceId;
/// <summary>
/// Initializes a new instance of the <see cref="AggregateRoot"/> class.
/// </summary>
/// <param name="eventSourceId">The <see cref="Events.EventSourceId" />.</param>
[Obsolete("This base constructor is deprecated and only used to set the EventSourceId property so that it could be used in the constructor")]
protected AggregateRoot(EventSourceId eventSourceId)
: this()
{
EventSourceId = eventSourceId;
}
/// <summary>
/// Initializes a new instance of the <see cref="AggregateRoot"/> class.
/// </summary>
protected AggregateRoot()
{
AggregateRootId = this.GetAggregateRootId();
Version = AggregateRootVersion.Initial;
IsStateless = this.IsStateless();
Expand All @@ -43,7 +52,22 @@ protected AggregateRoot(EventSourceId eventSourceId)
/// <summary>
/// Gets the <see cref="Events.EventSourceId" /> that the <see cref="AggregateRoot" /> applies events to.
/// </summary>
public EventSourceId EventSourceId { get; }
[Obsolete("This will eventually be marked as internal. If you need to know the event source id in the aggregate root then include it in the constructor and keep it as a field")]
public EventSourceId EventSourceId
{
get => _eventSourceId ?? throw new EventSourceIdOnAggregateRootNotReady(GetType());
internal set
{
if (_eventSourceId is null)
{
_eventSourceId = value;
}
else if (_eventSourceId.Value != value.Value)
{
throw new CannotChangeEventSourceIdForAggregateRoot(GetType(), _eventSourceId, value);
}
}
}

/// <summary>
/// Gets the <see cref="IEnumerable{T}" /> of applied events to commit.
Expand Down
24 changes: 24 additions & 0 deletions Source/Aggregates/AggregateRootOperationFailed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Events;

namespace Dolittle.SDK.Aggregates;

/// <summary>
/// Exception that gets thrown when an <see cref="AggregateRootOperations{TAggregate}.Perform(System.Action{TAggregate},System.Threading.CancellationToken)"/> failed.
/// </summary>
public class AggregateRootOperationFailed : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="AggregateRootOperationFailed"/> class.
/// </summary>
/// <param name="aggregateRootType">The <see cref="Type"/> of the <see cref="AggregateRoot"/>.</param>
/// <param name="eventSource">The <see cref="EventSourceId"/> of the aggregate.</param>
/// <param name="error">The inner <see cref="Exception"/> reason for failure.</param>
public AggregateRootOperationFailed(Type aggregateRootType, EventSourceId eventSource, Exception error)
: base($"Failed to perform operation on {aggregateRootType} aggregate with event source {eventSource}", error)
{
}
}
19 changes: 11 additions & 8 deletions Source/Aggregates/AggregateRootOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class AggregateRootOperations<TAggregate> : IAggregateRootOperations<TAgg
readonly IEventStore _eventStore;
readonly IEventTypes _eventTypes;
readonly IAggregateRoots _aggregateRoots;
readonly IServiceProvider _serviceProvider;
readonly ILogger _logger;

/// <summary>
Expand All @@ -35,18 +36,20 @@ public class AggregateRootOperations<TAggregate> : IAggregateRootOperations<TAgg
/// <param name="eventStore">The <see cref="IEventStore" /> used for committing the <see cref="UncommittedAggregateEvents" /> when actions are performed on the <typeparamref name="TAggregate">aggregate</typeparamref>. </param>
/// <param name="eventTypes">The <see cref="IEventTypes"/>.</param>
/// <param name="aggregateRoots">The <see cref="IAggregateRoots"/> used for getting an aggregate root instance.</param>
/// <param name="serviceProvider">The tenant scoped <see cref="IServiceProvider"/>.</param>
/// <param name="logger">The <see cref="ILogger" />.</param>
public AggregateRootOperations(EventSourceId eventSourceId, IEventStore eventStore, IEventTypes eventTypes, IAggregateRoots aggregateRoots, ILogger logger)
public AggregateRootOperations(EventSourceId eventSourceId, IEventStore eventStore, IEventTypes eventTypes, IAggregateRoots aggregateRoots, IServiceProvider serviceProvider, ILogger logger)
{
_eventSourceId = eventSourceId;
_eventTypes = eventTypes;
_eventStore = eventStore;
_aggregateRoots = aggregateRoots;
_serviceProvider = serviceProvider;
_logger = logger;
}

/// <inheritdoc/>
public Task Perform(Action<TAggregate> method, CancellationToken cancellationToken)
public Task Perform(Action<TAggregate> method, CancellationToken cancellationToken = default)
=> Perform(
aggregate =>
{
Expand All @@ -56,16 +59,16 @@ public Task Perform(Action<TAggregate> method, CancellationToken cancellationTok
cancellationToken);

/// <inheritdoc/>
public async Task Perform(Func<TAggregate, Task> method, CancellationToken cancellationToken)
public async Task Perform(Func<TAggregate, Task> method, CancellationToken cancellationToken = default)
{
using var activity = Tracing.ActivitySource.StartActivity()
?.Tag(_eventSourceId);

try
{
if (!TryGetAggregateRoot(_eventSourceId, out var aggregateRoot, out var exception))
if (!TryGetAggregateRoot(out var aggregateRoot, out var exception))
{
throw new CouldNotGetAggregateRoot(typeof(TAggregate), _eventSourceId, exception.Message);
throw new CouldNotGetAggregateRoot(typeof(TAggregate), _eventSourceId, exception);
}

var aggregateRootId = aggregateRoot.GetAggregateRootId();
Expand All @@ -81,13 +84,13 @@ public async Task Perform(Func<TAggregate, Task> method, CancellationToken cance
catch (Exception e)
{
activity?.RecordError(e);
throw;
throw new AggregateRootOperationFailed(typeof(TAggregate), _eventSourceId, e);
}
}

bool TryGetAggregateRoot(EventSourceId eventSourceId, out TAggregate aggregateRoot, out Exception exception)
bool TryGetAggregateRoot(out TAggregate aggregateRoot, out Exception exception)
{
var getAggregateRoot = _aggregateRoots.TryGet<TAggregate>(eventSourceId);
var getAggregateRoot = _aggregateRoots.TryGet<TAggregate>(_eventSourceId, _serviceProvider);
aggregateRoot = getAggregateRoot.Result;
exception = getAggregateRoot.Exception;
return getAggregateRoot.Success;
Expand Down
10 changes: 8 additions & 2 deletions Source/Aggregates/Builders/Aggregates.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Aggregates.Internal;
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Store;
using Dolittle.SDK.Tenancy;
using Microsoft.Extensions.Logging;

namespace Dolittle.SDK.Aggregates.Builders;
Expand All @@ -16,6 +18,7 @@ public class Aggregates : IAggregates
readonly IEventStore _eventStore;
readonly IEventTypes _eventTypes;
readonly IAggregateRoots _aggregateRoots;
readonly IServiceProvider _serviceProvider;
readonly ILoggerFactory _loggerFactory;

/// <summary>
Expand All @@ -24,12 +27,14 @@ public class Aggregates : IAggregates
/// <param name="eventStore">The <see cref="IEventStore"/>.</param>
/// <param name="eventTypes">The <see cref="IEventTypes"/>.</param>
/// <param name="aggregateRoots">The <see cref="IAggregateRoots"/>.</param>
/// <param name="serviceProvider">The tenant scoped <see cref="IServiceProvider"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public Aggregates(IEventStore eventStore, IEventTypes eventTypes, IAggregateRoots aggregateRoots, ILoggerFactory loggerFactory)
public Aggregates(IEventStore eventStore, IEventTypes eventTypes, IAggregateRoots aggregateRoots, IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
{
_eventStore = eventStore;
_eventTypes = eventTypes;
_aggregateRoots = aggregateRoots;
_serviceProvider = serviceProvider;
_loggerFactory = loggerFactory;
}

Expand All @@ -41,10 +46,11 @@ public IAggregateRootOperations<TAggregateRoot> Get<TAggregateRoot>(EventSourceI
_eventStore,
_eventTypes,
_aggregateRoots,
_serviceProvider,
_loggerFactory.CreateLogger<AggregateRootOperations<TAggregateRoot>>());

/// <inheritdoc />
public IAggregateOf<TAggregateRoot> Of<TAggregateRoot>()
where TAggregateRoot : AggregateRoot
=> new AggregateOf<TAggregateRoot>(this);
}
}
10 changes: 7 additions & 3 deletions Source/Aggregates/Builders/AggregatesBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Aggregates.Internal;
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Store.Builders;
Expand All @@ -16,6 +17,7 @@ public class AggregatesBuilder : IAggregatesBuilder
{
readonly IEventTypes _eventTypes;
readonly IAggregateRoots _aggregateRoots;
readonly Func<TenantId, IServiceProvider> _getServiceProvider;
readonly IEventStoreBuilder _eventStoreBuilder;
readonly ILoggerFactory _loggerFactory;

Expand All @@ -25,16 +27,18 @@ public class AggregatesBuilder : IAggregatesBuilder
/// <param name="eventStoreBuilder">The <see cref="IEventStoreBuilder" />.</param>
/// <param name="eventTypes">The <see cref="IEventTypes" />.</param>
/// <param name="aggregateRoots">The <see cref="IAggregateRoots"/>.</param>
/// <param name="getServiceProvider">The <see cref="Func{TResult}"/> for getting the tenant scoped <see cref="IServiceProvider"/> for a <see cref="TenantId"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory" />.</param>
public AggregatesBuilder(IEventStoreBuilder eventStoreBuilder, IEventTypes eventTypes, IAggregateRoots aggregateRoots, ILoggerFactory loggerFactory)
public AggregatesBuilder(IEventStoreBuilder eventStoreBuilder, IEventTypes eventTypes, IAggregateRoots aggregateRoots, Func<TenantId, IServiceProvider> getServiceProvider, ILoggerFactory loggerFactory)
{
_eventTypes = eventTypes;
_aggregateRoots = aggregateRoots;
_getServiceProvider = getServiceProvider;
_eventStoreBuilder = eventStoreBuilder;
_loggerFactory = loggerFactory;
}

/// <inheritdoc />
public IAggregates ForTenant(TenantId tenant)
=> new Aggregates(_eventStoreBuilder.ForTenant(tenant), _eventTypes, _aggregateRoots, _loggerFactory);
}
=> new Aggregates(_eventStoreBuilder.ForTenant(tenant), _eventTypes, _aggregateRoots, _getServiceProvider(tenant), _loggerFactory);
}
7 changes: 4 additions & 3 deletions Source/Aggregates/CouldNotCreateAggregateRootInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Events;

namespace Dolittle.SDK.Aggregates;

Expand All @@ -14,8 +15,8 @@ public class CouldNotCreateAggregateRootInstance : Exception
/// Initializes a new instance of the <see cref="CouldNotCreateAggregateRootInstance"/> class.
/// </summary>
/// <param name="type">The <see cref="Type" /> of the aggregate root that could not be instantiated.</param>
public CouldNotCreateAggregateRootInstance(Type type)
: base($"Could not create an instance of aggregate root {type}")
public CouldNotCreateAggregateRootInstance(Type type, EventSourceId eventSource, Exception error)
: base($"Could not create an instance of aggregate root {type} with event source {eventSource}", error)
{
}
}
}
8 changes: 4 additions & 4 deletions Source/Aggregates/CouldNotGetAggregateRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public class CouldNotGetAggregateRoot : Exception
/// </summary>
/// <param name="type">The <see cref="Type" /> of the aggregate root.</param>
/// <param name="eventSourceId">The <see cref="EventSourceId" />.</param>
/// <param name="reason">The reason for why the aggregate root could not be retrieved.</param>
public CouldNotGetAggregateRoot(Type type, EventSourceId eventSourceId, string reason)
: base($"Could not get aggregate root of type {type} with event source id {eventSourceId}. {reason}")
/// <param name="error">The inner error <see cref="Exception"/>.</param>
public CouldNotGetAggregateRoot(Type type, EventSourceId eventSourceId, Exception error)
: base($"Could not get aggregate root of type {type} with event source id {eventSourceId}", error)
{
}
}
}
22 changes: 22 additions & 0 deletions Source/Aggregates/EventSourceIdOnAggregateRootNotReady.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Dolittle.SDK.Events;

namespace Dolittle.SDK.Aggregates;

/// <summary>
/// Exception that gets thrown when trying to get the <see cref="EventSourceId"/> for an <see cref="AggregateRoot"/> where the value has not been set yet.
/// </summary>
public class EventSourceIdOnAggregateRootNotReady : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="EventSourceIdOnAggregateRootNotReady"/> class.
/// </summary>
/// <param name="aggregateRootType">The <see cref="Type"/> of the aggregate root.</param>
public EventSourceIdOnAggregateRootNotReady(Type aggregateRootType)
: base($"Event Source has not yet been set on the {aggregateRootType} aggregate root instance. This typically happens when trying to use the {nameof(AggregateRoot.EventSourceId)} property in the constructor." +
$" If this is important then all you need to do is to include in the public constructor a parameter with the {typeof(EventSourceId)} type and use that in the bast constructor, then the {nameof(AggregateRoot.EventSourceId)} property will be accessible in the constructor.")
{}
}
Loading

0 comments on commit 557e671

Please sign in to comment.