Skip to content

Commit 705e9b5

Browse files
Feature/ddd (#159)
* Add DDD entity abstractions design spec Defines the design for AggregateRoot, DomainEntity, ValueObject, and IDomainEvent types to be added to RCommon.Entities. Extends existing BusinessEntity hierarchy with zero breaking changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update DDD design spec with review feedback Address spec review findings: add [Serializable] and [ConcurrencyCheck], use block-scoped namespaces, add non-generic IAggregateRoot marker, fix DomainEvents allocation via dual-list approach, thread-safe GetHashCode, document graph walker behavior and known limitations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address remaining spec review items Add IEquatable<DomainEntity<TKey>> implementation, default! initializer for Id, expand known-limitation documentation for dual-list sync bypass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add DDD entity abstractions implementation plan TDD implementation plan for AggregateRoot, DomainEntity, ValueObject, and IDomainEvent. 5 chunks, 25 steps, 69 tests covering all spec scenarios including integration with IEntityEventTracker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add IDomainEvent interface and DomainEvent base record IDomainEvent extends ISerializableEvent with EventId and OccurredOn. DomainEvent is an abstract record with sensible defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add ValueObject abstract record C# record-based value object with automatic structural equality, immutability, and with-expression support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add DomainEntity<TKey> base class Lightweight entity with identity-based equality and IEquatable support. No event tracking — child entities raise events through aggregate root. * feat: add AggregateRoot<TKey> and IAggregateRoot interfaces Extends BusinessEntity<TKey> with domain event management, versioning, and optimistic concurrency. Domain events flow through existing IEntityEventTracker pipeline via AddLocalEvent delegation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove EntityEquals override from AggregateRoot The 'new' keyword hiding of EntityEquals created polymorphic inconsistency — callers through IBusinessEntity would get binary comparison while callers through AggregateRoot got Id comparison. Better tracked as a future improvement making EntityEquals virtual on BusinessEntity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix async issues: ConfigureAwait(false), CancellationToken propagation, InMemoryEventBus memory leak - Add ConfigureAwait(false) to ~150 await calls across all library projects - Add CancellationToken to IEventBus.PublishAsync, IEventRouter.RouteEventsAsync, and IEmailService.SendEmailAsync interfaces - Propagate CancellationToken through DapperRepository.OpenAsync (13 sites), Linq2DbRepository.DeleteAsync, all MediatR/Wolverine/MassTransit handler bridges, and the event routing pipeline - Refactor InMemoryEventBus to not capture IServiceCollection (memory leak), track dynamic subscriptions internally and resolve via ActivatorUtilities - Fix SendGridEmailService.SendEmailAsync missing return on empty recipients guard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add aggregate repository design spec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: expand DDD spec to cover event dispatch, read models, and sagas Adds three new design sections to the aggregate repository spec: - Part 2: Automatic domain event dispatch via UnitOfWork post-commit hook - Part 3: Read-model repositories (IReadModelRepository<T>, IPagedResult<T>) - Part 4: Saga/process manager patterns (ISaga, IStateMachine, ISagaStore) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Implemented DDD related persistence. * Updated Masstransit to use non-commercial version. Removed Autmapper as it has gone commercial. * Updated specification pattern to be more comprehensive. * Enabled ValueObject for DDD * Added state machine implementations for Stateless and MassTransit. * feat: add outbox core abstractions (IOutboxMessage, IOutboxStore, IOutboxSerializer, OutboxOptions) * feat: add JsonOutboxSerializer with round-trip serialization and type safety Implement JsonOutboxSerializer as IOutboxSerializer with: - Serialize: Convert ISerializableEvent to JSON using System.Text.Json - GetEventTypeName: Return short assembly-qualified type name for deserialization - Deserialize: Reconstruct ISerializableEvent from type name and payload with validation All 5 tests pass with coverage of happy path and error cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: two-phase UnitOfWork commit with PersistEventsAsync and CancellationToken propagation - Add PersistEventsAsync(CT) to IEntityEventTracker for outbox phase-1 write - Add CancellationToken to EmitTransactionalEventsAsync signature - Implement PersistEventsAsync as no-op in InMemoryEntityEventTracker - Pass CancellationToken through to IEventRouter.RouteEventsAsync - Refactor UnitOfWork.CommitAsync to three-phase: persist events, commit tx, dispatch events - Add Microsoft.Extensions.Hosting.Abstractions package refs to RCommon.Persistence.csproj - Update all test mocks/verifies to match new interface signatures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add OutboxEventRouter with buffer-persist-dispatch pattern Implements OutboxEventRouter as the transactional outbox IEventRouter: buffers events in-memory via AddTransactionalEvent(s), persists them as OutboxMessage rows via PersistBufferedEventsAsync (Phase 1, within active transaction), and dispatches pending messages via RouteEventsAsync (Phase 3, post-commit). Includes 6 unit tests covering buffering, persistence, field correctness, dispatch, and failure marking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add OutboxEntityEventTracker decorator for two-phase event persistence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add OutboxProcessingService background poller with retry and dead-letter support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add AddOutbox<T> builder extension, UnitOfWork integration, and concurrency tests Implements the AddOutbox<TOutboxStore>() extension on IPersistenceBuilder that wires all outbox services (store, serializer, router, entity tracker, background processor) into the DI container with correct lifetimes. Adds UnitOfWork integration test verifying the two-phase PersistEventsAsync flow through OutboxEntityEventTracker, plus edge-case concurrency tests for empty buffers, no-pending routing, and dead-letter exclusion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add EFCoreOutboxStore with IDataStoreFactory, RetryCount filter, and SQLite tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add DapperOutboxStore with IDataStoreFactory and RetryCount filter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Linq2DbOutboxStore with IDataStoreFactory and RetryCount filter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add RCommon.MassTransit.Outbox wrapping native EF Core outbox Introduces RCommon.MassTransit.Outbox project with IMassTransitOutboxBuilder, MassTransitOutboxBuilder, and AddOutbox<TDbContext> extension method that wraps MassTransit's IEntityFrameworkOutboxConfigurator into RCommon's builder pattern. Includes test project with 4 passing unit tests covering UseSqlServer, UsePostgres, UseBusOutbox delegation, and null-guard constructor validation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add RCommon.Wolverine.Outbox wrapping native durable messaging Wraps Wolverine's WolverineFx.EntityFrameworkCore durable messaging with RCommon's builder pattern via IWolverineOutboxBuilder, WolverineOutboxBuilder, and WolverineOutboxBuilderExtensions. Includes test project with 8 passing unit tests achieving 100% line/branch/method coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add outbox projects to solution file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: throttle outbox cleanup and document EF Core client-side ordering - Add CleanupInterval option (default 1h) to avoid running DELETE queries on every polling cycle - Add clear comment explaining why EFCoreOutboxStore uses client-side OrderBy (DateTimeOffset not supported in ORDER BY by all providers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: ThenInclude chain was overwritten by RepositoryQuery getter The RepositoryQuery getter always replaced _repositoryQuery with _includableQueryable, discarding any ThenInclude results. Fixed by nulling _includableQueryable after ThenInclude consumes it, and having Include chain from _repositoryQuery when _includableQueryable is null (preserving prior ThenInclude chains). Affects both EFCoreRepository and EFCoreAggregateRepository. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add Outbox V2 design spec Covers exponential backoff, distributed locking (SQL Server + PostgreSQL), dead letter replay, and inbox/idempotency with standalone and auto-check modes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: fix SQL Server ClaimAsync SQL and spec review issues - Use CTE instead of UPDATE TOP for ordered claims - Add UPDLOCK, ROWLOCK, READPAST hints for concurrent safety - Clarify ConfigureAwait(false) requirement in pseudocode - Fix inbox ConsumerType nullability description - Add missing Dapper/Linq2Db builder extension files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: address spec review round 2 — OutboxEventRouter changes - Add Section 4.4 documenting OutboxEventRouter V2 behavior - RouteEventsAsync() no longer reads from store; dispatches retained events - Failures left for background processor (no MarkFailedAsync call) - Add missing test files to Section 8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: fix spec review round 3 — router sketch and inbox ID clarity - Add implementation sketch for OutboxEventRouter retained-event pattern - Fix Mode 1 inbox example: use domain-specific ID, not @event.Id - Clarify that ISerializableEvent has no Id property - Document that Mode 1 requires consumer-chosen deduplication key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add Outbox V2 implementation plan 12-task plan covering backoff strategy, interface changes, inbox abstractions, OutboxEventRouter V2, OutboxProcessingService V2, EF Core/Dapper/Linq2Db store implementations, inbox stores, and full verification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add IBackoffStrategy, ExponentialBackoffStrategy, and V2 OutboxOptions Introduces IBackoffStrategy interface and ExponentialBackoffStrategy implementation for computing retry delays with configurable base delay, max delay, and multiplier. Extends OutboxOptions with V2 properties: LockDuration, BackoffBaseDelay, BackoffMaxDelay, BackoffMultiplier, and InboxTableName. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: V2 interface changes — ClaimAsync, backoff, locking, dead letter replay Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add IInboxStore, IInboxMessage, and InboxMessage abstractions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: OutboxEventRouter V2 — retained-event dispatch, no store reads Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: OutboxProcessingService V2 — ClaimAsync, backoff, inbox auto-check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: EFCoreOutboxStore V2 — ClaimAsync, dead letter replay, backoff - Add ClaimAsync with optimistic LINQ fallback (works with SQLite in tests) - Remove GetPendingAsync, replaced by ClaimAsync with locking semantics - Update MarkFailedAsync to accept nextRetryAtUtc, clear lock fields - Add GetDeadLettersAsync with offset/batchSize pagination - Add ReplayDeadLetterAsync to reset all fields on dead-lettered messages - Update SaveAsync to copy NextRetryAtUtc, LockedByInstanceId, LockedUntilUtc - Add _tableName field from OutboxOptions - Update OutboxMessageConfiguration with new columns and two new indexes - Rewrite EFCoreOutboxStoreTests with 12 tests covering V2 behaviors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: EFCoreInboxStore — inbox/idempotency for EF Core Adds EFCoreInboxStore (IInboxStore), InboxMessageConfiguration (composite PK on MessageId+ConsumerType), AddInboxMessages ModelBuilder extension, and 4 passing tests. TestOutboxDbContext updated to include inbox schema. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: DapperOutboxStore V2 — ClaimAsync, dead letter replay, backoff Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: DapperInboxStore — inbox/idempotency for Dapper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: Linq2DbOutboxStore V2 — ClaimAsync, dead letter replay, backoff - Add ILockStatementProvider to constructor with null guard - Remove GetPendingAsync; add ClaimAsync with PostgreSQL/SQL Server raw SQL - Update SaveAsync to copy NextRetryAtUtc, LockedByInstanceId, LockedUntilUtc - Update MarkFailedAsync signature to include nextRetryAtUtc and clear locks - Add GetDeadLettersAsync (LINQ) and ReplayDeadLetterAsync (LINQ update) - Add UseLockStatementProvider<TProvider> to ILinq2DbPersistenceBuilder and Linq2DbPersistenceBuilder - Update tests: add lock provider mock and NullLockStatementProvider test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: Linq2DbInboxStore — inbox/idempotency for Linq2Db Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Added outbox implementations for the stack. * Moved projects to appropriate solution folder. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b83bdcd commit 705e9b5

219 files changed

Lines changed: 21178 additions & 632 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/HR.LeaveManagement.Application.UnitTests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="AutoMapper" Version="16.0.0" />
1211
<PackageReference Include="Bogus" Version="35.6.5" />
1312
<PackageReference Include="FluentAssertions" Version="8.8.0" />
1413
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.2" />

Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/LeaveTypes/Commands/CreateLeaveTypeCommandHandlerTests.cs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs.LeaveType;
32
using HR.LeaveManagement.Application.Exceptions;
43
using HR.LeaveManagement.Application.Features.LeaveTypes.Handlers.Commands;
54
using HR.LeaveManagement.Application.Features.LeaveTypes.Handlers.Queries;
65
using HR.LeaveManagement.Application.Features.LeaveTypes.Requests.Commands;
76
using HR.LeaveManagement.Application.Features.LeaveTypes.Requests.Queries;
8-
using HR.LeaveManagement.Application.Profiles;
97
using HR.LeaveManagement.Application.Responses;
108
using HR.LeaveManagement.Domain;
119
using Moq;
@@ -26,21 +24,11 @@ namespace HR.LeaveManagement.Application.UnitTests.LeaveTypes.Commands
2624
[TestFixture()]
2725
public class CreateLeaveTypeCommandHandlerTests
2826
{
29-
private readonly IMapper _mapper;
30-
3127
private readonly CreateLeaveTypeDto _leaveTypeDto;
3228
private readonly CreateLeaveTypeCommandHandler _handler;
3329

3430
public CreateLeaveTypeCommandHandlerTests()
3531
{
36-
37-
var mapperConfig = new MapperConfiguration(c =>
38-
{
39-
c.AddProfile<MappingProfile>();
40-
}, null);
41-
42-
_mapper = mapperConfig.CreateMapper();
43-
4432
var testData = new List<LeaveType>();
4533
var mock = new Mock<IGraphRepository<LeaveType>>();
4634
var validationMock = new Mock<IValidationService>();
@@ -56,7 +44,7 @@ public CreateLeaveTypeCommandHandlerTests()
5644

5745
validationMock.Setup(x => x.ValidateAsync(_leaveTypeDto, false, CancellationToken.None))
5846
.Returns(() => Task.FromResult(new ValidationOutcome()));
59-
_handler = new CreateLeaveTypeCommandHandler(_mapper, mock.Object, validationMock.Object);
47+
_handler = new CreateLeaveTypeCommandHandler(mock.Object, validationMock.Object);
6048
}
6149

6250
[Test]
@@ -77,7 +65,7 @@ public async Task InValid_LeaveType_Added()
7765
//leaveTypes.Count.ShouldBe(3);
7866

7967
result.ShouldBeOfType<BaseCommandResponse>();
80-
68+
8169
}
8270
}
8371
}

Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/LeaveTypes/Queries/GetLeaveTypeListRequestHandlerTests.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs.LeaveType;
32
using HR.LeaveManagement.Application.Features.LeaveTypes.Handlers.Queries;
43
using HR.LeaveManagement.Application.Features.LeaveTypes.Requests.Queries;
5-
using HR.LeaveManagement.Application.Profiles;
64
using HR.LeaveManagement.Domain;
75
using Moq;
86
using NUnit.Framework;
@@ -22,19 +20,6 @@ namespace HR.LeaveManagement.Application.UnitTests.LeaveTypes.Queries
2220
[TestFixture()]
2321
public class GetLeaveTypeListRequestHandlerTests
2422
{
25-
private readonly IMapper _mapper;
26-
public GetLeaveTypeListRequestHandlerTests()
27-
{
28-
//_mockRepo = MockLeaveTypeRepository.GetLeaveTypeRepository();
29-
30-
var mapperConfig = new MapperConfiguration(c =>
31-
{
32-
c.AddProfile<MappingProfile>();
33-
}, null);
34-
35-
_mapper = mapperConfig.CreateMapper();
36-
}
37-
3823
[Test]
3924
public async Task GetLeaveTypeListTest()
4025
{
@@ -46,8 +31,8 @@ public async Task GetLeaveTypeListTest()
4631
var mock = new Mock<IGraphRepository<LeaveType>>();
4732
mock.Setup(x => x.FindAsync(x=>true, CancellationToken.None))
4833
.Returns(() => Task.FromResult(testData as ICollection<LeaveType>));
49-
50-
var handler = new GetLeaveTypeListRequestHandler(mock.Object, _mapper);
34+
35+
var handler = new GetLeaveTypeListRequestHandler(mock.Object);
5136
var result = await handler.HandleAsync(new GetLeaveTypeListRequest(), CancellationToken.None);
5237

5338
result.ShouldBeOfType<List<LeaveTypeDto>>();

Examples/CleanWithCQRS/HR.LeaveManagement.Application/ApplicationServicesRegistration.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using HR.LeaveManagement.Application.Profiles;
21
using Microsoft.Extensions.DependencyInjection;
32
using System;
43
using System.Collections.Generic;
@@ -11,8 +10,6 @@ public static class ApplicationServicesRegistration
1110
{
1211
public static IServiceCollection ConfigureApplicationServices(this IServiceCollection services)
1312
{
14-
services.AddAutoMapper(x => x.AddProfile(new MappingProfile()));
15-
1613
return services;
1714
}
1815
}

Examples/CleanWithCQRS/HR.LeaveManagement.Application/Features/LeaveAllocations/Handlers/Commands/CreateLeaveAllocationCommandHandler.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs.LeaveAllocation.Validators;
32
using HR.LeaveManagement.Application.Exceptions;
43
using HR.LeaveManagement.Application.Features.LeaveAllocations.Requests.Commands;
@@ -25,21 +24,18 @@ public class CreateLeaveAllocationCommandHandler : IAppRequestHandler<CreateLeav
2524
private readonly IGraphRepository<LeaveType> _leaveTypeRepository;
2625
private readonly IGraphRepository<LeaveAllocation> _leaveAllocationRepository;
2726
private readonly IUserService _userService;
28-
private readonly IMapper _mapper;
2927
private readonly IValidationService _validationService;
3028

3129
public CreateLeaveAllocationCommandHandler(IGraphRepository<LeaveType> leaveTypeRepository,
3230
IGraphRepository<LeaveAllocation> leaveAllocationRepository,
3331
IUserService userService,
34-
IMapper mapper,
3532
IValidationService validationService)
3633
{
3734
this._leaveTypeRepository = leaveTypeRepository;
3835
this._leaveAllocationRepository = leaveAllocationRepository;
3936
this._leaveAllocationRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
4037
this._leaveTypeRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
4138
this._userService = userService;
42-
_mapper = mapper;
4339
_validationService = validationService;
4440
}
4541

@@ -77,12 +73,11 @@ public async Task<BaseCommandResponse> HandleAsync(CreateLeaveAllocationCommand
7773
{
7874
await _leaveAllocationRepository.AddAsync(item);
7975
}
80-
76+
8177
response.Success = true;
8278
response.Message = "Allocations Successful";
8379
}
8480

85-
8681
return response;
8782
}
8883
}

Examples/CleanWithCQRS/HR.LeaveManagement.Application/Features/LeaveAllocations/Handlers/Commands/DeleteLeaveAllocationCommandHandler.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.Exceptions;
32
using HR.LeaveManagement.Application.Features.LeaveAllocations.Requests.Commands;
43
using HR.LeaveManagement.Application.Features.LeaveTypes.Requests.Commands;
@@ -17,13 +16,11 @@ namespace HR.LeaveManagement.Application.Features.LeaveAllocations.Handlers.Comm
1716
public class DeleteLeaveAllocationCommandHandler : IAppRequestHandler<DeleteLeaveAllocationCommand>
1817
{
1918
private readonly IGraphRepository<LeaveAllocation> _leaveAllocationRepository;
20-
private readonly IMapper _mapper;
2119

22-
public DeleteLeaveAllocationCommandHandler(IGraphRepository<LeaveAllocation> leaveAllocationRepository, IMapper mapper)
20+
public DeleteLeaveAllocationCommandHandler(IGraphRepository<LeaveAllocation> leaveAllocationRepository)
2321
{
2422
this._leaveAllocationRepository = leaveAllocationRepository;
2523
this._leaveAllocationRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
26-
_mapper = mapper;
2724
}
2825

2926
public async Task HandleAsync(DeleteLeaveAllocationCommand request, CancellationToken cancellationToken)

Examples/CleanWithCQRS/HR.LeaveManagement.Application/Features/LeaveAllocations/Handlers/Commands/UpdateLeaveAllocationCommandHandler.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs.LeaveAllocation.Validators;
32
using HR.LeaveManagement.Application.Exceptions;
43
using HR.LeaveManagement.Application.Features.LeaveAllocations.Requests.Commands;
54
using HR.LeaveManagement.Application.Features.LeaveTypes.Requests.Commands;
5+
using HR.LeaveManagement.Application.Mappings;
66
using HR.LeaveManagement.Domain;
77
using RCommon.Mediator.Subscribers;
88
using System;
@@ -20,19 +20,16 @@ public class UpdateLeaveAllocationCommandHandler : IAppRequestHandler<UpdateLeav
2020
{
2121
private readonly IGraphRepository<LeaveAllocation> _leaveAllocationRepository;
2222
private readonly IReadOnlyRepository<LeaveType> _leaveTypeRepository;
23-
private readonly IMapper _mapper;
2423
private readonly IValidationService _validationService;
2524

2625
public UpdateLeaveAllocationCommandHandler(IGraphRepository<LeaveAllocation> leaveAllocationRepository,
2726
IReadOnlyRepository<LeaveType> leaveTypeRepository,
28-
IMapper mapper,
2927
IValidationService validationService)
3028
{
3129
this._leaveAllocationRepository = leaveAllocationRepository;
3230
this._leaveTypeRepository = leaveTypeRepository;
3331
this._leaveAllocationRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
3432
this._leaveTypeRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
35-
_mapper = mapper;
3633
_validationService = validationService;
3734
}
3835

@@ -48,10 +45,9 @@ public async Task HandleAsync(UpdateLeaveAllocationCommand request, Cancellation
4845
if (leaveAllocation is null)
4946
throw new NotFoundException(nameof(leaveAllocation), request.LeaveAllocationDto.Id);
5047

51-
_mapper.Map(request.LeaveAllocationDto, leaveAllocation);
48+
request.LeaveAllocationDto.ApplyTo(leaveAllocation);
5249

5350
await _leaveAllocationRepository.UpdateAsync(leaveAllocation);
54-
5551
}
5652
}
5753
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs;
32
using HR.LeaveManagement.Application.DTOs.LeaveAllocation;
43
using HR.LeaveManagement.Application.Features.LeaveAllocations.Requests.Queries;
4+
using HR.LeaveManagement.Application.Mappings;
55
using HR.LeaveManagement.Domain;
66
using RCommon.Mediator.Subscribers;
77
using RCommon.Persistence;
@@ -14,19 +14,18 @@ namespace HR.LeaveManagement.Application.Features.LeaveAllocations.Handlers.Quer
1414
public class GetLeaveAllocationDetailRequestHandler : IAppRequestHandler<GetLeaveAllocationDetailRequest, LeaveAllocationDto>
1515
{
1616
private readonly IGraphRepository<LeaveAllocation> _leaveAllocationRepository;
17-
private readonly IMapper _mapper;
1817

19-
public GetLeaveAllocationDetailRequestHandler(IGraphRepository<LeaveAllocation> leaveAllocationRepository, IMapper mapper)
18+
public GetLeaveAllocationDetailRequestHandler(IGraphRepository<LeaveAllocation> leaveAllocationRepository)
2019
{
2120
_leaveAllocationRepository = leaveAllocationRepository;
2221
this._leaveAllocationRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
23-
_mapper = mapper;
2422
}
23+
2524
public async Task<LeaveAllocationDto> HandleAsync(GetLeaveAllocationDetailRequest request, CancellationToken cancellationToken)
2625
{
2726
_leaveAllocationRepository.Include(x => x.LeaveType);
2827
var leaveAllocation = await _leaveAllocationRepository.FindAsync(request.Id);
29-
return _mapper.Map<LeaveAllocationDto>(leaveAllocation);
28+
return leaveAllocation.ToLeaveAllocationDto();
3029
}
3130
}
3231
}

Examples/CleanWithCQRS/HR.LeaveManagement.Application/Features/LeaveAllocations/Handlers/Queries/GetLeaveAllocationListRequestHandler.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs;
32
using HR.LeaveManagement.Application.DTOs.LeaveAllocation;
43
using HR.LeaveManagement.Application.Features.LeaveAllocations.Requests.Queries;
4+
using HR.LeaveManagement.Application.Mappings;
55
using RCommon.Mediator.Subscribers;
66
using System.Collections.Generic;
7+
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using HR.LeaveManagement.Application.Contracts.Identity;
@@ -19,18 +20,15 @@ namespace HR.LeaveManagement.Application.Features.LeaveAllocations.Handlers.Quer
1920
public class GetLeaveAllocationListRequestHandler : IAppRequestHandler<GetLeaveAllocationListRequest, List<LeaveAllocationDto>>
2021
{
2122
private readonly IGraphRepository<LeaveAllocation> _leaveAllocationRepository;
22-
private readonly IMapper _mapper;
2323
private readonly IHttpContextAccessor _httpContextAccessor;
2424
private readonly IUserService _userService;
2525

2626
public GetLeaveAllocationListRequestHandler(IGraphRepository<LeaveAllocation> leaveAllocationRepository,
27-
IMapper mapper,
2827
IHttpContextAccessor httpContextAccessor,
2928
IUserService userService)
3029
{
3130
_leaveAllocationRepository = leaveAllocationRepository;
3231
this._leaveAllocationRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
33-
_mapper = mapper;
3432
this._httpContextAccessor = httpContextAccessor;
3533
this._userService = userService;
3634
}
@@ -44,19 +42,19 @@ public async Task<List<LeaveAllocationDto>> HandleAsync(GetLeaveAllocationListRe
4442
{
4543
var userId = _httpContextAccessor.HttpContext.User.FindFirst(
4644
q => q.Type == CustomClaimTypes.Uid)?.Value;
47-
leaveAllocations = await _leaveAllocationRepository.FindAsync(x=>x.EmployeeId == userId) as List<LeaveAllocation>;
45+
leaveAllocations = await _leaveAllocationRepository.FindAsync(x => x.EmployeeId == userId) as List<LeaveAllocation>;
4846

4947
var employee = await _userService.GetEmployee(userId);
50-
allocations = _mapper.Map<List<LeaveAllocationDto>>(leaveAllocations);
48+
allocations = leaveAllocations.Select(x => x.ToLeaveAllocationDto()).ToList();
5149
foreach (var alloc in allocations)
5250
{
5351
alloc.Employee = employee;
5452
}
5553
}
5654
else
5755
{
58-
leaveAllocations = await _leaveAllocationRepository.FindAsync(x=>true) as List<LeaveAllocation>;
59-
allocations = _mapper.Map<List<LeaveAllocationDto>>(leaveAllocations);
56+
leaveAllocations = await _leaveAllocationRepository.FindAsync(x => true) as List<LeaveAllocation>;
57+
allocations = leaveAllocations.Select(x => x.ToLeaveAllocationDto()).ToList();
6058
foreach (var req in allocations)
6159
{
6260
req.Employee = await _userService.GetEmployee(req.EmployeeId);

Examples/CleanWithCQRS/HR.LeaveManagement.Application/Features/LeaveRequests/Handlers/Commands/CreateLeaveRequestCommandHandler.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using AutoMapper;
21
using HR.LeaveManagement.Application.DTOs.LeaveRequest.Validators;
32
using HR.LeaveManagement.Application.Exceptions;
43
using HR.LeaveManagement.Application.Features.LeaveRequests.Requests.Commands;
54
using HR.LeaveManagement.Application.Features.LeaveTypes.Requests.Commands;
5+
using HR.LeaveManagement.Application.Mappings;
66
using HR.LeaveManagement.Application.Responses;
77
using HR.LeaveManagement.Domain;
88
using RCommon.Mediator.Subscribers;
@@ -32,7 +32,6 @@ public class CreateLeaveRequestCommandHandler : IAppRequestHandler<CreateLeaveRe
3232
private readonly IEmailService _emailSender;
3333
private readonly ICurrentUser _currentUser;
3434
private readonly IOptions<SendGridEmailSettings> _emailSettings;
35-
private readonly IMapper _mapper;
3635
private readonly IValidationService _validationService;
3736
private readonly IReadOnlyRepository<LeaveType> _leaveTypeRepository;
3837
private readonly IGraphRepository<LeaveAllocation> _leaveAllocationRepository;
@@ -45,7 +44,6 @@ public CreateLeaveRequestCommandHandler(
4544
IEmailService emailSender,
4645
ICurrentUser currentUser,
4746
IOptions<SendGridEmailSettings> emailSettings,
48-
IMapper mapper,
4947
IValidationService validationService)
5048
{
5149
_leaveTypeRepository = leaveTypeRepository;
@@ -56,8 +54,7 @@ public CreateLeaveRequestCommandHandler(
5654
this._leaveRequestRepository.DataStoreName = DataStoreNamesConst.LeaveManagement;
5755
_emailSender = emailSender;
5856
this._currentUser = currentUser;
59-
_emailSettings=emailSettings;
60-
_mapper = mapper;
57+
_emailSettings = emailSettings;
6158
_validationService = validationService;
6259
}
6360

@@ -67,8 +64,8 @@ public async Task<BaseCommandResponse> HandleAsync(CreateLeaveRequestCommand req
6764
var validationResult = await _validationService.ValidateAsync(request.LeaveRequestDto);
6865
var userId = _currentUser.FindClaimValue(CustomClaimTypes.Uid);
6966

70-
var allocation = _leaveAllocationRepository.FirstOrDefault(x=>x.EmployeeId == userId && x.LeaveTypeId == request.LeaveRequestDto.LeaveTypeId);
71-
if(allocation is null)
67+
var allocation = _leaveAllocationRepository.FirstOrDefault(x => x.EmployeeId == userId && x.LeaveTypeId == request.LeaveRequestDto.LeaveTypeId);
68+
if (allocation is null)
7269
{
7370
validationResult.Errors.Add(new ValidationFault(nameof(request.LeaveRequestDto.LeaveTypeId),
7471
"You do not have any allocations for this leave type."));
@@ -82,7 +79,7 @@ public async Task<BaseCommandResponse> HandleAsync(CreateLeaveRequestCommand req
8279
nameof(request.LeaveRequestDto.EndDate), "You do not have enough days for this request"));
8380
}
8481
}
85-
82+
8683
if (validationResult.IsValid == false)
8784
{
8885
response.Success = false;
@@ -91,7 +88,7 @@ public async Task<BaseCommandResponse> HandleAsync(CreateLeaveRequestCommand req
9188
}
9289
else
9390
{
94-
var leaveRequest = _mapper.Map<LeaveRequest>(request.LeaveRequestDto);
91+
var leaveRequest = request.LeaveRequestDto.ToLeaveRequest();
9592
leaveRequest.RequestingEmployeeId = userId;
9693
await _leaveRequestRepository.AddAsync(leaveRequest);
9794
//TODO: May need to get Id out
@@ -104,7 +101,7 @@ public async Task<BaseCommandResponse> HandleAsync(CreateLeaveRequestCommand req
104101
{
105102
var emailAddress = _currentUser.FindClaimValue(ClaimTypes.Email);
106103

107-
var email = new MailMessage(new MailAddress(this._emailSettings.Value.FromEmailDefault, this._emailSettings.Value.FromNameDefault),
104+
var email = new MailMessage(new MailAddress(this._emailSettings.Value.FromEmailDefault, this._emailSettings.Value.FromNameDefault),
108105
new MailAddress(emailAddress))
109106
{
110107
Body = $"Your leave request for {request.LeaveRequestDto.StartDate:D} to {request.LeaveRequestDto.EndDate:D} " +
@@ -119,7 +116,7 @@ public async Task<BaseCommandResponse> HandleAsync(CreateLeaveRequestCommand req
119116
//// Log or handle error, but don't throw...
120117
}
121118
}
122-
119+
123120
return response;
124121
}
125122
}

0 commit comments

Comments
 (0)