@@ -7,11 +7,12 @@ namespace BridgingIT.DevKit.Examples.GettingStarted.Modules.CoreModule.Applicati
77
88using BridgingIT . DevKit . Examples . GettingStarted . Modules . CoreModule . Domain . Events ;
99using BridgingIT . DevKit . Examples . GettingStarted . Modules . CoreModule . Domain . Model ;
10+ using MediatR ;
1011using Microsoft . Extensions . Logging ;
1112
1213/// <summary>
1314/// Handler for <see cref="CustomerUpdateCommand"/>.
14- /// Maps DTO → domain, checks business rules, updates the entity in the repository,
15+ /// Maps DTO -> domain, checks business rules, updates the entity in the repository,
1516/// and maps back to <see cref="CustomerModel"/> for returning to the client.
1617/// </summary>
1718/// <remarks>
@@ -48,19 +49,11 @@ await repository.FindOneResultAsync(CustomerId.Create(request.Model.Id), cancell
4849 . Add ( RuleSet . IsNotEmpty ( e . FirstName ) ) // also validated in domain
4950 . Add ( RuleSet . IsNotEmpty ( e . LastName ) ) // also validated in domain
5051 . Add ( RuleSet . NotEqual ( e . LastName , "notallowed" ) ) // also validated in domain
51- //.Add(new EmailShouldBeUniqueRule(e.Email, repository)) // TODO: Check unique email excluding the current entity (currently disabled)
52+ //.Add(new EmailShouldBeUniqueRule(e.Email, repository)) // TODO: Check unique email excluding the current entity (currently disabled)
5253 . CheckAsync ( cancellationToken ) , cancellationToken : cancellationToken )
5354
54- // STEP 3 - Apply changes to Aggregate
55- . Bind ( e => e . ChangeName ( request . Model . FirstName , request . Model . LastName ) )
56- . Bind ( e => e . ChangeEmail ( request . Model . Email ) )
57- . Bind ( e => e . ChangeBirthDate ( request . Model . DateOfBirth ) )
58- . Bind ( e => e . ChangeStatus ( request . Model . Status ) )
59- . Bind ( e => this . UpdateAddresses ( e , request . Model . Addresses ) )
60- . Tap ( e => // set concurrency version for optimistic concurrency check
61- {
62- e . ConcurrencyVersion = Guid . Parse ( request . Model . ConcurrencyVersion ) ;
63- } )
55+ // STEP 3 - Apply changes to Aggregate from request model
56+ . Bind ( e => this . UpdateAggregate ( e , request . Model ) )
6457
6558 // STEP 4 — Save updated Aggregate to repository
6659 . BindAsync ( async ( e , ct ) =>
@@ -69,65 +62,74 @@ await repository.UpdateResultAsync(e, ct), cancellationToken)
6962 // STEP 5 — Side effects (audit/logging)
7063 . Log ( logger , "AUDIT - Customer {Id} updated for {Email}" , r => [ r . Value . Id , r . Value . Email . Value ] )
7164
72- // STEP 6 — Map updated Aggregate → Model
65+ // STEP 6 — Map updated Aggregate -> Model
7366 . MapResult < Customer , CustomerModel > ( mapper )
74- . Log ( logger , "Entity mapped to {@Model}" , r => [ r . Value ] ) ;
67+ . Log ( logger , "Aggregate mapped to {@Model}" , r => [ r . Value ] ) ;
68+
69+ /// <summary>
70+ /// Updates the customer's basic properties (name, email, birth date, status).
71+ /// </summary>
72+ /// <param name="customer">The customer aggregate to update.</param>
73+ /// <param name="model">The customer model containing the updated values.</param>
74+ /// <returns>The updated customer wrapped in a Result.</returns>
75+ private Result < Customer > UpdateAggregate ( Customer customer , CustomerModel model ) =>
76+ // Setup the customer update chain.
77+ Result < Customer > . Success ( customer )
78+ . Bind ( e => e . ChangeName ( model . FirstName , model . LastName ) )
79+ . Bind ( e => e . ChangeEmail ( model . Email ) )
80+ . Bind ( e => e . ChangeBirthDate ( model . DateOfBirth ) )
81+ . Bind ( e => e . ChangeStatus ( model . Status ) )
82+ . Bind ( e => this . ChangeAddresses ( customer , model ) )
83+ . Tap ( e => e . ConcurrencyVersion = Guid . Parse ( model . ConcurrencyVersion ) ) ; // set concurrency version for optimistic concurrency check
7584
7685 /// <summary>
7786 /// Processes address changes by comparing the specified addresses with existing ones.
7887 /// Removes Customer addresses not in the specified addresses, adds new addresses and updates existing addresses.
7988 /// </summary>
8089 /// <param name="customer">The customer aggregate to update.</param>
81- /// <param name="addressModels ">The addresses from the update request .</param>
90+ /// <param name="model ">The customer model containing the updated values .</param>
8291 /// <returns>The updated customer wrapped in a Result.</returns>
83- private Result < Customer > UpdateAddresses ( Customer customer , List < CustomerAddressModel > addressModels )
92+ private Result < Customer > ChangeAddresses ( Customer customer , CustomerModel model )
8493 {
85- addressModels ??= [ ] ;
94+ // Setup the address update chain.
95+ return Result < Customer > . Success ( customer )
96+ . When ( _ => model . Addresses . SafeAny ( ) , r => r
97+ . Bind ( c => RemoveMissing ( c , model . Addresses ) )
98+ . Bind ( c => AddOrChange ( c , model . Addresses ) )
99+ . Bind ( c => SetPrimary ( c , model . Addresses ) ) ) ;
86100
87- // Extract valid address IDs from request using LINQ fluent style
88- var addressIds = addressModels
89- . Where ( m => ! string . IsNullOrWhiteSpace ( m . Id ) )
90- . Select ( m => AddressId . Create ( m . Id ) ) . ToList ( ) ;
91-
92- // Remove obsolete addresses - find and return first failure if any
93- var removeResult = customer . Addresses
94- . Select ( a => a . Id ) . Except ( addressIds )
95- . Select ( customer . RemoveAddress ) . FirstOrDefault ( r => r . IsFailure ) . Unwrap ( ) ; // TODO: use .Flatten()
96-
97- // Early return on first failure using fluent When extension
98- if ( removeResult . IsFailure )
101+ // Removes addresses from the customer that are not present in the provided address models.
102+ Result < Customer > RemoveMissing ( Customer customer , List < CustomerAddressModel > addressModels )
99103 {
100- return removeResult ;
104+ var keepIds = addressModels
105+ . Where ( m => ! string . IsNullOrWhiteSpace ( m . Id ) )
106+ . Select ( m => AddressId . Create ( m . Id ) ) . ToList ( ) ;
107+ var removeIds = customer . Addresses . Select ( a => a . Id ) . Except ( keepIds ) . ToList ( ) ;
108+ foreach ( var id in removeIds )
109+ {
110+ var result = customer . RemoveAddress ( id ) ;
111+ if ( result . IsFailure ) return result ;
112+ }
113+ return Result < Customer > . Success ( customer ) ;
101114 }
102115
103- // Process each address: add new or update existing
104- foreach ( var addressModel in addressModels )
116+ // Processes the provided address models to add new addresses or update existing ones.
117+ Result < Customer > AddOrChange ( Customer customer , List < CustomerAddressModel > addressModels )
105118 {
106- var result = ProcessAddress ( customer , addressModel ) ;
107- if ( result . IsFailure )
119+ foreach ( var m in addressModels )
108120 {
109- return result ;
121+ var result = string . IsNullOrWhiteSpace ( m . Id )
122+ ? customer . AddAddress ( m . Name , m . Line1 , m . Line2 , m . PostalCode , m . City , m . Country )
123+ : customer . ChangeAddress ( m . Id , m . Name , m . Line1 , m . Line2 , m . PostalCode , m . City , m . Country ) ;
124+ if ( result . IsFailure ) return result ;
110125 }
126+ return Result < Customer > . Success ( customer ) ;
111127 }
112128
113- // Set primary address if specified
114- return addressModels
115- . Find ( m => m . IsPrimary )
116- . Match (
129+ // Sets the primary address of the customer based on the provided address models.
130+ Result < Customer > SetPrimary ( Customer customer , List < CustomerAddressModel > addressModels ) =>
131+ addressModels . Find ( m => m . IsPrimary ) . Match (
117132 some : m => customer . SetPrimaryAddress ( m . Id ) ,
118133 none : ( ) => Result < Customer > . Success ( customer ) ) ;
119-
120- Result < Customer > ProcessAddress ( Customer customer , CustomerAddressModel model ) =>
121- string . IsNullOrWhiteSpace ( model . Id )
122- ? customer . AddAddress (
123- model . Name , model . Line1 , model . Line2 ,
124- model . PostalCode , model . City , model . Country )
125- : customer . Addresses
126- . Find ( a => a . Id == AddressId . Create ( model . Id ) )
127- . Match (
128- some : _ => customer . ChangeAddress (
129- model . Id , model . Name , model . Line1 , model . Line2 ,
130- model . PostalCode , model . City , model . Country ) ,
131- none : ( ) => Result < Customer > . Success ( customer ) ) ;
132134 }
133135}
0 commit comments