Skip to content

Commit 43da44f

Browse files
author
vp
committed
Enhance error handling with Result type
- Added or restored MIT license headers and namespaces. - Updated CustomerCreateCommandHandler to use Result<CustomerCreateContext> for better error handling. - Replaced Tap with Bind in CustomerUpdateStatusCommandHandler for improved error management. - Removed unnecessary space in CoreModule.Domain.csproj. - Modified Customer creation in CoreModuleSeedEntities to handle potential failures. - Reintroduced Customer, CustomerNumber, and CustomerStatus classes with Result<T> for error handling. - Changed EmailAddress.Create to return Result<EmailAddress>. - Updated conversion logic in CustomerTypeConfiguration and CoreModuleMapperRegister to handle Result type. - Adjusted tests to accommodate Result type handling.
1 parent 8856508 commit 43da44f

12 files changed

Lines changed: 68 additions & 45 deletions

File tree

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
namespace BridgingIT.DevKit.Examples.GettingStarted.Modules.CoreModule.Application;
77

8-
using System;
98
using BridgingIT.DevKit.Examples.GettingStarted.Modules.CoreModule.Domain.Model;
109
using Microsoft.Extensions.Logging;
10+
using System;
1111

1212
/// <summary>
1313
/// Handler for <see cref="CustomerCreateCommand"/> that performs business validation,
@@ -56,16 +56,16 @@ await Result<CustomerModel>
5656

5757
.Log(logger, "Customer number created{@Number}", r => [r.Value.Number])
5858

59-
// STEP 4 — Create aggregate
59+
// STEP 4 — Create new Aggregate
6060
.Bind(this.CreateEntity)
6161

62-
// STEP 6 — Save aggregate to repository
62+
// STEP 6 — Save new Aggregate to repository
6363
.BindResultAsync(this.PersistEntityAsync, this.CapturePersistedEntity, cancellationToken)
6464

6565
// STEP 7 — Side effects (audit/logging)
6666
.Log(logger, "AUDIT - Customer {Id} created for {Email}", r => [r.Value.Entity.Id, r.Value.Entity.Email.Value])
6767

68-
// STEP 8 — Map domain → DTO
68+
// STEP 8 — Map new Aggregate → Model
6969
.Map(this.ToModel)
7070
.Log(logger, "Mapped to {@Model}", r => [r.Value]);
7171

@@ -81,13 +81,15 @@ private CustomerCreateContext CaptureNumber(CustomerCreateContext ctx, long seq)
8181
return ctx;
8282
}
8383

84-
private CustomerCreateContext CreateEntity(CustomerCreateContext ctx)
84+
private Result<CustomerCreateContext> CreateEntity(CustomerCreateContext ctx)
8585
{
86-
ctx.Entity = Customer.Create(
87-
ctx.Model.FirstName,
88-
ctx.Model.LastName,
89-
ctx.Model.Email,
90-
ctx.Number);
86+
var createResult = Customer.Create(ctx.Model.FirstName, ctx.Model.LastName, ctx.Model.Email, ctx.Number);
87+
if (createResult.IsFailure)
88+
{
89+
return createResult.Unwrap();
90+
}
91+
92+
ctx.Entity = createResult.Value;
9193
return ctx;
9294
}
9395

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override async Task<Result<CustomerModel>> HandleAsync(
2525
await repository.FindOneResultAsync(CustomerId.Create(request.CustomerId), cancellationToken: cancellationToken)
2626

2727
// Change status (idempotent if same)
28-
.Tap(e => e.ChangeStatus(request.Status))
28+
.Bind(e => e.ChangeStatus(request.Status))
2929

3030
// Update in repository
3131
.BindAsync(async (e, ct) =>

src/Modules/CoreModule/CoreModule.Domain/CoreModule.Domain.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="BridgingIT.DevKit.Domain" />
15-
<PackageReference Include="BridgingIT.DevKit.Domain.CodeGen" >
15+
<PackageReference Include="BridgingIT.DevKit.Domain.CodeGen">
1616
<PrivateAssets>all</PrivateAssets>
1717
<IncludeAssets>analyzers</IncludeAssets>
1818
</PackageReference>

src/Modules/CoreModule/CoreModule.Domain/CoreModuleSeedEntities.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static class CoreModuleSeedEntities
1717
public static Customer[] CreateCustomer() => [
1818
..new[]
1919
{
20-
Customer.Create("John", "Doe", "john.doe@example.com", CustomerNumber.Create(DateTime.Now, 100000)),
21-
Customer.Create("Mary", "Jane", "mary.jane@example.com", CustomerNumber.Create(DateTime.Now, 100001))
20+
Customer.Create("John", "Doe", "john.doe@example.com", CustomerNumber.Create(DateTime.Now.AddYears(-1), 100000)).Value,
21+
Customer.Create("Mary", "Jane", "mary.jane@example.com", CustomerNumber.Create(DateTime.Now.AddYears(-1), 100001)).Value
2222
}.ForEach(e => e.Id = GuidGenerator.Create($"Customer_{e.Email.Value}"))];
2323
}

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

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,15 @@ private Customer(string firstName, string lastName, EmailAddress email, Customer
7979
/// <param name="lastName">The last name of the customer.</param>
8080
/// <param name="email">The email address of the customer.</param>
8181
/// <returns>A new <see cref="Customer"/> instance.</returns>
82-
public static Customer Create(string firstName, string lastName, string email, CustomerNumber number)
82+
public static Result<Customer> Create(string firstName, string lastName, string email, CustomerNumber number)
8383
{
84-
var customer = new Customer(firstName, lastName, EmailAddress.Create(email), number);
84+
var emailAddressResult = EmailAddress.Create(email);
85+
if (emailAddressResult.IsFailure)
86+
{
87+
return emailAddressResult.Unwrap();
88+
}
89+
90+
var customer = new Customer(firstName, lastName, emailAddressResult.Value, number);
8591

8692
customer.DomainEvents.Register(
8793
new CustomerCreatedDomainEvent(customer));
@@ -97,19 +103,16 @@ public static Customer Create(string firstName, string lastName, string email, C
97103
/// <param name="firstName">The new first name (optional).</param>
98104
/// <param name="lastName">The new last name (optional).</param>
99105
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
100-
public Customer ChangeName(string firstName, string lastName)
106+
public Result<Customer> ChangeName(string firstName, string lastName)
101107
{
102108
if (string.IsNullOrEmpty(firstName) &&
103109
string.IsNullOrEmpty(lastName))
104110
{
105-
return this;
111+
return Result<Customer>.Failure(this, "Invalid name");
106112
}
107113

108-
this.ApplyChange(this.FirstName, firstName, v => this.FirstName = v);
109-
110-
this.ApplyChange(this.LastName, lastName, v => this.LastName = v);
111-
112-
return this;
114+
return this.ApplyChange(this.FirstName, firstName, v => this.FirstName = v)
115+
.Bind(c => c.ApplyChange(this.LastName, lastName, v => this.LastName = v));
113116
}
114117

115118
/// <summary>
@@ -118,16 +121,20 @@ public Customer ChangeName(string firstName, string lastName)
118121
/// </summary>
119122
/// <param name="email">The new email address.</param>
120123
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
121-
public Customer ChangeEmail(string email)
124+
public Result<Customer> ChangeEmail(string email)
122125
{
123126
if (string.IsNullOrEmpty(email))
124127
{
125-
return this;
128+
return Result<Customer>.Failure(this, "Invalid email");
126129
}
127130

128-
var newEmail = EmailAddress.Create(email);
131+
var emailResult = EmailAddress.Create(email);
132+
if (emailResult.IsFailure)
133+
{
134+
return emailResult.Unwrap();
135+
}
129136

130-
return this.ApplyChange(this.Email, newEmail, v => this.Email = v);
137+
return this.ApplyChange(this.Email, emailResult.Value, v => this.Email = v);
131138
}
132139

133140
/// <summary>
@@ -136,11 +143,11 @@ public Customer ChangeEmail(string email)
136143
/// </summary>
137144
/// <param name="status">The new customer status.</param>
138145
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
139-
public Customer ChangeStatus(CustomerStatus status)
146+
public Result<Customer> ChangeStatus(CustomerStatus status)
140147
{
141148
if (status == default)
142149
{
143-
return this;
150+
return Result<Customer>.Failure(this, "Invalid status");
144151
}
145152

146153
return this.ApplyChange(this.Status, status, v => this.Status = v);
@@ -156,16 +163,18 @@ public Customer ChangeStatus(CustomerStatus status)
156163
/// <param name="newValue">The new value to apply.</param>
157164
/// <param name="action">The assignment action if a change occurs.</param>
158165
/// <returns>The current <see cref="Customer"/> instance for chaining.</returns>
159-
private Customer ApplyChange<T>(T currentValue, T newValue, Action<T> action)
166+
private Result<Customer> ApplyChange<T>(T currentValue, T newValue, Action<T> action)
160167
{
161168
if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
162169
{
163-
return this; // nothing to do, keep current
170+
return Result<Customer>.Success(this); // nothing to do, keep current
164171
}
165172

166173
action(newValue);
167174

168-
this.DomainEvents.Register(new CustomerUpdatedDomainEvent(this), true);
175+
this.DomainEvents.Register(
176+
new CustomerUpdatedDomainEvent(this), true);
177+
169178
return this;
170179
}
171180
}

src/Modules/CoreModule/CoreModule.Domain/Model/CustomerNumber.cs renamed to src/Modules/CoreModule/CoreModule.Domain/Model/CustomerAggregate/CustomerNumber.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace BridgingIT.DevKit.Examples.GettingStarted.Modules.CoreModule.Domain.Model;
77

88
[DebuggerDisplay("Value={Value}")]
9-
public class CustomerNumber : ValueObject
9+
public class CustomerNumber : ValueObject // TODO: refactor to use Result<CustomerNumber> for the create methods
1010
{
1111
private static readonly Regex FormatRegex =
1212
new(@"^CUS-(\d{4})-(\d{6})$", RegexOptions.Compiled | RegexOptions.IgnoreCase);

src/Modules/CoreModule/CoreModule.Domain/Model/CustomerStatus.cs renamed to src/Modules/CoreModule/CoreModule.Domain/Model/CustomerAggregate/CustomerStatus.cs

File renamed without changes.

src/Modules/CoreModule/CoreModule.Domain/Model/EmailAddress.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,21 @@ private EmailAddress()
4848
/// Thrown if <paramref name="value"/> is not a valid email format.
4949
/// </exception>
5050
/// <returns>A new <see cref="EmailAddress"/> value object.</returns>
51-
public static EmailAddress Create(string value)
51+
public static Result<EmailAddress> Create(string value)
5252
{
5353
value = value?.Trim()?.ToLowerInvariant();
5454

55-
Rule.Add(RuleSet.IsValidEmail(value))
56-
.Throw();
55+
var ruleResult = Rule.Add(RuleSet.IsValidEmail(value))
56+
.Check();
5757

58-
return new EmailAddress(value);
58+
if (ruleResult.IsFailure)
59+
{
60+
return Result<EmailAddress>.Failure()
61+
.WithMessages(ruleResult.Messages)
62+
.WithErrors(ruleResult.Errors);
63+
}
64+
65+
return new EmailAddress(value); // implicitly wrapped in a successful Result
5966
}
6067

6168
/// <summary>
@@ -68,4 +75,4 @@ protected override IEnumerable<object> GetAtomicValues()
6875
{
6976
yield return this.Value;
7077
}
71-
}
78+
}

src/Modules/CoreModule/CoreModule.Infrastructure/EntityFramework/Configurations/CustomerTypeConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void Configure(EntityTypeBuilder<Customer> builder)
6161
.IsRequired()
6262
.HasConversion(
6363
email => email.Value, // when saving
64-
value => EmailAddress.Create(value)) // when loading
64+
value => EmailAddress.Create(value).Value) // when loading
6565
.HasMaxLength(256);
6666

6767
// Map CustomerStatus enumeration → int in database using custom converter

src/Modules/CoreModule/CoreModule.Presentation/CoreModuleMapperRegister.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public void Register(TypeAdapterConfig config)
3030

3131
// Map string -> EmailAddress (for reconstructing value object on input)
3232
config.NewConfig<string, EmailAddress>()
33-
.MapWith(src => EmailAddress.Create(src));
33+
.MapWith(src => EmailAddress.Create(src).Value);
3434

3535
// ----------------------------
3636
// Enumeration conversions

0 commit comments

Comments
 (0)