From 29ac52e4d5e41d37c34e51c78d3bf06a818c0c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Thu, 26 Nov 2020 11:07:47 +0300 Subject: [PATCH] Documented: Business Logic & Exceptions in the Entities --- ...main-Driven-Design-Implementation-Guide.md | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md index 9f5fce6618..e8f576efca 100644 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/en/Domain-Driven-Design-Implementation-Guide.md @@ -492,4 +492,135 @@ namespace IssueTracking.Issues ##### Business Logic & Exceptions in the Entities -TODO \ No newline at end of file +When you implement validation and business logic in the entities, you frequently need to manage exceptional cases. In these cases; + +* Create **domain specific exceptions**. +* **Throw these exceptions** in the entity methods when necessary. + +**Example** + +````csharp +public class Issue : AggregateRoot +{ + //... + + public bool IsLocked { get; private set; } + public bool IsClosed { get; private set; } + public IssueCloseReason? CloseReason { get; private set; } + + public void Close(IssueCloseReason reason) + { + IsClosed = true; + CloseReason = reason; + } + + public void ReOpen() + { + if (IsLocked) + { + throw new IssueStateException( + "Can not open a locked issue! Unlock it first." + ); + } + + IsClosed = false; + CloseReason = null; + } + + public void Lock() + { + if (!IsClosed) + { + throw new IssueStateException( + "Can not open a locked issue! Unlock it first." + ); + } + + IsLocked = true; + } + + public void Unlock() + { + IsLocked = false; + } +} +```` + +There are two business rules here; + +* A locked issue can not be re-opened. +* You can not lock an open issue. + +`Issue` class throws a `IssueStateException` in these cases to force business rules: + +````csharp +using System; + +namespace IssueTracking.Issues +{ + public class IssueStateException : Exception + { + public IssueStateException(string message) + : base(message) + { + + } + } +} +```` + +There are two potential problems of throwing such exceptions; + +1. In case of such an exception, should the **end user** see the exception (error) message? If so, how do you **localize** the exception message? You can not use the [localization](Localization.md) system, because you can't inject and use `IStringLocalizer` in the entities. +2. For a web application or HTTP API, what **HTTP Status Code** should return to the client? + +ABP's [Exception Handling](Exception-Handling.md) system solves these problems (and more). + +**Example: Throwing a business exception with code** + +````csharp +using Volo.Abp; + +namespace IssueTracking.Issues +{ + public class IssueStateException : BusinessException + { + public IssueStateException(string code) + : base(code) + { + + } + } +} +```` + +* `IssueStateException` class inherits the `BusinessException` class. ABP returns 403 (forbidden) HTTP Status code by default (instead of 500 - Internal Server Error) for the exceptions derived from the `BusinessException`. +* The `code` is used as a key in the localization resource file to find the localized message. + +Now, we can change the `ReOpen` method as shown below: + +````csharp +public void ReOpen() +{ + if (IsLocked) + { + throw new IssueStateException("IssueTracking:CanNotOpenLockedIssue"); + } + + IsClosed = false; + CloseReason = null; +} +```` + +> Use constants instead of magic strings. + +And add an entry to the localization resource. Example entry for the English language: + +````json +"IssueTracking:CanNotOpenLockedIssue": "Can not open a locked issue! Unlock it first." +```` + +* ABP automatically uses this localized message (based on the current language) to show to the end user when you throw the exception. +* The exception code (`IssueTracking:CanNotOpenLockedIssue` here) is also sent to the client, so it may handle the error case programmatically. + +> For this example, you could directly throw `BusinessException` instead of defining a specialized `IssueStateException`. The result will be same. See the [exception handling document](Exception-Handling.md) for all the details. \ No newline at end of file