Skip to content

Commit a42b675

Browse files
committed
Refactor address update logic; enforce single primary address
- Require exactly one primary address in update validation. - Remove isPrimary from AddAddress; set primary separately. - Refactor address update/removal to use functional style. - Rename and improve address processing methods for clarity. - Update BridgingIT.DevKit package versions. - Minor doc and header improvements.
1 parent 6f22734 commit a42b675

5 files changed

Lines changed: 103 additions & 108 deletions

File tree

Directory.Packages.props

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
8-
<PackageVersion Include="BridgingIT.DevKit.Application.JobScheduling" Version="10.0.2-preview.0.56" />
9-
<PackageVersion Include="BridgingIT.DevKit.Common.Mapping" Version="10.0.2-preview.0.56" />
10-
<PackageVersion Include="BridgingIT.DevKit.Common.Utilities.Xunit" Version="10.0.2-preview.0.56" />
11-
<PackageVersion Include="BridgingIT.DevKit.Domain" Version="10.0.2-preview.0.56" />
12-
<PackageVersion Include="BridgingIT.DevKit.Domain.CodeGen" Version="10.0.2-preview.0.56" />
13-
<PackageVersion Include="BridgingIT.DevKit.Infrastructure.EntityFramework" Version="10.0.2-preview.0.56" />
14-
<PackageVersion Include="BridgingIT.DevKit.Infrastructure.EntityFramework.SqlServer" Version="10.0.2-preview.0.56" />
15-
<PackageVersion Include="BridgingIT.DevKit.Presentation.Web" Version="10.0.2-preview.0.56" />
16-
<PackageVersion Include="BridgingIT.DevKit.Presentation.Web.JobScheduling" Version="10.0.2-preview.0.56" />
17-
<PackageVersion Include="BridgingIT.DevKit.Presentation.Serilog" Version="10.0.2-preview.0.56" />
8+
<PackageVersion Include="BridgingIT.DevKit.Application.JobScheduling" Version="10.0.2-preview.0.64" />
9+
<PackageVersion Include="BridgingIT.DevKit.Common.Mapping" Version="10.0.2-preview.0.64" />
10+
<PackageVersion Include="BridgingIT.DevKit.Common.Utilities.Xunit" Version="10.0.2-preview.0.64" />
11+
<PackageVersion Include="BridgingIT.DevKit.Domain" Version="10.0.2-preview.0.64" />
12+
<PackageVersion Include="BridgingIT.DevKit.Domain.CodeGen" Version="10.0.2-preview.0.64" />
13+
<PackageVersion Include="BridgingIT.DevKit.Infrastructure.EntityFramework" Version="10.0.2-preview.0.64" />
14+
<PackageVersion Include="BridgingIT.DevKit.Infrastructure.EntityFramework.SqlServer" Version="10.0.2-preview.0.64" />
15+
<PackageVersion Include="BridgingIT.DevKit.Presentation.Web" Version="10.0.2-preview.0.64" />
16+
<PackageVersion Include="BridgingIT.DevKit.Presentation.Web.JobScheduling" Version="10.0.2-preview.0.64" />
17+
<PackageVersion Include="BridgingIT.DevKit.Presentation.Serilog" Version="10.0.2-preview.0.64" />
1818
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
1919
<PackageVersion Include="Dumpify" Version="0.6.6" />
2020
<PackageVersion Include="FluentAssertions.Web" Version="1.9.5" />

src/Modules/CoreModule/CoreModule.Application/Commands/CustomerCreateCommandHandler.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ private Result<CustomerCreateContext> CreateEntity(CustomerCreateContext ctx)
102102
addressModel.Line2,
103103
addressModel.PostalCode,
104104
addressModel.City,
105-
addressModel.Country,
106-
addressModel.IsPrimary));
105+
addressModel.Country));
107106
}
108107

109108
return r;

src/Modules/CoreModule/CoreModule.Application/Commands/CustomerUpdateCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public Validator()
3636

3737
// Address validation rules
3838
this.RuleFor(c => c.Model.Addresses)
39-
.Must(addresses => addresses == null || addresses.Count(a => a.IsPrimary) <= 1)
40-
.WithMessage("Only one address can be marked as primary");
39+
.Must(addresses => addresses == null || addresses.Count(a => a.IsPrimary) == 1)
40+
.WithMessage("One address should be marked as primary");
4141

4242
this.RuleForEach(c => c.Model.Addresses).ChildRules(address =>
4343
{
Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// MIT-License
1+
// MIT-License
22
// Copyright BridgingIT GmbH - All Rights Reserved
33
// Use of this source code is governed by an MIT-style license that can be
44
// found in the LICENSE file at https://github.com/bridgingit/bitdevkit/license
@@ -56,7 +56,7 @@ await repository.FindOneResultAsync(CustomerId.Create(request.Model.Id), cancell
5656
.Bind(e => e.ChangeEmail(request.Model.Email))
5757
.Bind(e => e.ChangeBirthDate(request.Model.DateOfBirth))
5858
.Bind(e => e.ChangeStatus(request.Model.Status))
59-
.Bind(e => this.ProcessAddressChanges(e, request.Model.Addresses))
59+
.Bind(e => this.UpdateAddresses(e, request.Model.Addresses))
6060
.Tap(e => // set concurrency version for optimistic concurrency check
6161
{
6262
e.ConcurrencyVersion = Guid.Parse(request.Model.ConcurrencyVersion);
@@ -74,78 +74,60 @@ await repository.UpdateResultAsync(e, ct), cancellationToken)
7474
.Log(logger, "Entity mapped to {@Model}", r => [r.Value]);
7575

7676
/// <summary>
77-
/// Processes address changes by comparing the incoming addresses with existing ones.
78-
/// Removes addresses not in the request, adds new addresses, and updates existing addresses.
77+
/// Processes address changes by comparing the specified addresses with existing ones.
78+
/// Removes Customer addresses not in the specified addresses, adds new addresses and updates existing addresses.
7979
/// </summary>
8080
/// <param name="customer">The customer aggregate to update.</param>
81-
/// <param name="requestAddresses">The addresses from the update request.</param>
81+
/// <param name="addressModels">The addresses from the update request.</param>
8282
/// <returns>The updated customer wrapped in a Result.</returns>
83-
private Result<Customer> ProcessAddressChanges(Customer customer, List<CustomerAddressModel> requestAddresses)
83+
private Result<Customer> UpdateAddresses(Customer customer, List<CustomerAddressModel> addressModels)
8484
{
85-
requestAddresses ??= [];
85+
addressModels ??= [];
8686

87-
// Get current address IDs
88-
var requestAddressIds = requestAddresses
89-
.Where(a => !string.IsNullOrWhiteSpace(a.Id))
90-
.Select(a => AddressId.Create(a.Id)).ToList();
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();
9191

92-
// Remove addresses not in request
93-
var addressesToRemove = customer.Addresses.Select(a => a.Id).Except(requestAddressIds).ToList();
94-
foreach (var addressId in addressesToRemove)
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)
9599
{
96-
var result = customer.RemoveAddress(addressId);
97-
if (result.IsFailure)
98-
{
99-
return result;
100-
}
100+
return removeResult;
101101
}
102102

103-
// Process each address in request
104-
foreach (var addressModel in requestAddresses)
103+
// Process each address: add new or update existing
104+
foreach (var addressModel in addressModels)
105105
{
106-
if (string.IsNullOrWhiteSpace(addressModel.Id))
107-
{
108-
// Add new address
109-
var result = customer.AddAddress(
110-
addressModel.Name,
111-
addressModel.Line1,
112-
addressModel.Line2,
113-
addressModel.PostalCode,
114-
addressModel.City,
115-
addressModel.Country,
116-
addressModel.IsPrimary);
117-
118-
if (result.IsFailure)
119-
{
120-
return result;
121-
}
122-
}
123-
else
106+
var result = ProcessAddress(customer, addressModel);
107+
if (result.IsFailure)
124108
{
125-
// Update existing address
126-
var addressId = AddressId.Create(Guid.Parse(addressModel.Id));
127-
var existingAddress = customer.Addresses.FirstOrDefault(a => a.Id == addressId);
128-
129-
if (existingAddress != null)
130-
{
131-
var result = customer.ChangeAddress(
132-
addressId,
133-
addressModel.Name,
134-
addressModel.Line1,
135-
addressModel.Line2,
136-
addressModel.PostalCode,
137-
addressModel.City,
138-
addressModel.Country,
139-
addressModel.IsPrimary);
140-
141-
if (result.IsFailure)
142-
{
143-
return result;
144-
}
145-
}
109+
return result;
146110
}
147111
}
148112

149-
return Result<Customer>.Success(customer);
113+
// Set primary address if specified
114+
return addressModels
115+
.Find(m => m.IsPrimary)
116+
.Match(
117+
some: m => customer.SetPrimaryAddress(m.Id),
118+
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));
150132
}
151133
}

src/Modules/CoreModule/CoreModule.Domain/Model/CustomerAggregate/Customer.cs

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace BridgingIT.DevKit.Examples.GettingStarted.Modules.CoreModule.Domain.Model;
77

8+
using System.Net.Sockets;
89
using BridgingIT.DevKit.Domain;
910

1011
/// <summary>
@@ -184,10 +185,10 @@ public Result<Customer> ChangeStatus(CustomerStatus status)
184185
/// <param name="country">The country name (required).</param>
185186
/// <param name="isPrimary">Indicates if this should be the primary address.</param>
186187
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
187-
public Result<Customer> AddAddress(string name, string line1, string line2, string postalCode, string city, string country, bool isPrimary = false)
188+
public Result<Customer> AddAddress(string name, string line1, string line2, string postalCode, string city, string country)
188189
{
189190
return this.Change()
190-
.Add(e => this.addresses, Address.Create(name, line1, line2, postalCode, city, country, isPrimary))
191+
.Add(e => this.addresses, Address.Create(name, line1, line2, postalCode, city, country))
191192
.Register(e => new CustomerUpdatedDomainEvent(e))
192193
.Apply();
193194
}
@@ -196,34 +197,23 @@ public Result<Customer> AddAddress(string name, string line1, string line2, stri
196197
/// Removes an address from the customer's address collection.
197198
/// Registers a <see cref="CustomerUpdatedDomainEvent"/>.
198199
/// </summary>
199-
/// <param name="addressId">The ID of the address to remove.</param>
200+
/// <param name="id">The ID of the address to remove.</param>
200201
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
201-
public Result<Customer> RemoveAddress(AddressId addressId)
202+
public Result<Customer> RemoveAddress(AddressId id)
202203
{
203-
var address = this.addresses.FirstOrDefault(a => a.Id == addressId);
204-
if (address == null)
205-
{
206-
return Result<Customer>.Failure($"Address with ID {addressId} not found");
207-
}
208-
209-
this.addresses.Remove(address);
210-
211204
return this.Change()
205+
//.When(_ => id != null)
206+
.RemoveById(e => this.addresses, id, errorMessage: "Address with specified ID not found")
212207
.Register(e => new CustomerUpdatedDomainEvent(e))
213208
.Apply();
214209
}
215210

216-
public Result<Customer> SetPrimaryAddress(AddressId addressId)
211+
public Result<Customer> SetPrimaryAddress(AddressId id)
217212
{
218213
return this.Change()
219-
.Ensure(_ => this.addresses.Any(a => a.Id == addressId), "Address with ID {addressId} not found")
220-
.Execute(e =>
221-
{
222-
foreach (var addr in this.addresses)
223-
{
224-
addr.SetPrimary(addr.Id == addressId);
225-
}
226-
})
214+
//.When(_ => id != null)
215+
.Ensure(_ => this.addresses.Any(a => a.Id == id), "Address with specified ID not found")
216+
.Set(e => e.addresses, a => a.SetPrimary(a.Id == id))
227217
.Register(e => new CustomerUpdatedDomainEvent(e))
228218
.Apply();
229219
}
@@ -232,33 +222,57 @@ public Result<Customer> SetPrimaryAddress(AddressId addressId)
232222
/// Updates an existing address with new values.
233223
/// Registers a <see cref="CustomerUpdatedDomainEvent"/>.
234224
/// </summary>
235-
/// <param name="addressId">The ID of the address to update.</param>
225+
/// <param name="id">The ID of the address to update.</param>
236226
/// <param name="name">The new name/label.</param>
237227
/// <param name="line1">The new first line.</param>
238228
/// <param name="line2">The new second line.</param>
239229
/// <param name="postalCode">The new postal code.</param>
240230
/// <param name="city">The new city.</param>
241231
/// <param name="country">The new country.</param>
242232
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
243-
public Result<Customer> ChangeAddress(AddressId addressId, string name, string line1, string line2, string postalCode, string city, string country)
233+
public Result<Customer> ChangeAddress(AddressId id, string name, string line1, string line2, string postalCode, string city, string country)
244234
{
245-
var address = this.addresses.FirstOrDefault(a => a.Id == addressId);
235+
var address = this.addresses.FirstOrDefault(a => a.Id == id);
246236
if (address == null)
247237
{
248-
return Result<Customer>.Failure($"Address with ID {addressId} not found");
238+
return Result<Customer>.Failure("Address with specified ID not found");
249239
}
250240

251241
return address.Change()
252-
.Ensure(_ => address != null, "Address with ID {addressId} not found")
253-
//.Ensure(_ => this.addresses.Any(a => a.Id == addressId), "Address with ID {addressId} not found")
242+
.Ensure(_ => address != null, "Address with specified ID not found")
243+
//.Ensure(_ => this.addresses.Any(a => a.Id == addressId), "Address with specified ID not found")
254244
.Set(e => e.ChangeName(name))
255245
.Set(e => e.ChangeLine1(line1))
256246
.Set(e => e.ChangeLine2(line2))
257247
.Set(e => e.ChangePostalCode(postalCode))
258248
.Set(e => e.ChangeCity(city))
259249
.Set(e => e.ChangeCountry(country))
260250
.Register(e => new CustomerUpdatedDomainEvent(this))
261-
.Apply();
251+
.Apply().Wrap(this);
252+
253+
// return customer.Change()
254+
// .Register(e => new CustomerUpdatedDomainEvent(this))
255+
// .Select(e => e.addresses.FirstOrDefault(a => a.Id == id), "Address with specified ID not found") // change to address context
256+
// .Set(a => a.ChangeName(name))
257+
// .Set(a => a.ChangeLine1(line1))
258+
// .Set(a => a.ChangeLine2(line2))
259+
// .Set(a => a.ChangePostalCode(postalCode))
260+
// .Set(a => a.ChangeCity(city))
261+
// .Set(a => a.ChangeCountry(country))
262+
// .Apply();
263+
264+
// return customer.Change()
265+
// .Ensure(_ => this.addresses.Any(a => a.Id == id), "Address with specified ID not found")
266+
// .Select(e => e.addresses.FirstOrDefault(a => a.Id == id) // change to address context
267+
// .Change()
268+
// .Set(a => a.ChangeName(name))
269+
// .Set(a => a.ChangeLine1(line1))
270+
// .Set(a => a.ChangeLine2(line2))
271+
// .Set(a => a.ChangePostalCode(postalCode))
272+
// .Set(a => a.ChangeCity(city))
273+
// .Set(a => a.ChangeCountry(country)).Apply())
274+
// .Register(e => new CustomerUpdatedDomainEvent(this))
275+
// .Apply();
262276

263277
// var nameResult = address.ChangeName(name);
264278
// if (nameResult.IsFailure)
@@ -296,8 +310,8 @@ public Result<Customer> ChangeAddress(AddressId addressId, string name, string l
296310
// return countryResult.Unwrap();
297311
// }
298312

299-
// return this.Change()
300-
// .Register(e => new CustomerUpdatedDomainEvent(e))
301-
// .Apply();
302-
}
303-
}
313+
// return this.Change()
314+
// .Register(e => new CustomerUpdatedDomainEvent(e))
315+
// .Apply();
316+
}
317+
}

0 commit comments

Comments
 (0)