diff --git a/1. Brokers/1. Brokers.md b/1. Brokers/1. Brokers.md index afcb005..35f0cdf 100644 --- a/1. Brokers/1. Brokers.md +++ b/1. Brokers/1. Brokers.md @@ -213,7 +213,7 @@ Here's a real-life implementation of an entire storage broker for all CRUD opera In order to abstract at an operational level all example implementations in this section will utilize the ValueTask type to provide an asynchronization abstraction. This approach encourages a consistent handling of asynchronous operations across all method implementations, promoting a clean and efficient abstraction that aligns with Standard principles. -### For IStorageBroker.cs: +##### For IStorageBroker.cs: ```csharp namespace OtripleS.Web.Api.Brokers.Storages { @@ -221,9 +221,10 @@ namespace OtripleS.Web.Api.Brokers.Storages { } } + ``` -### For StorageBroker.cs: +##### For StorageBroker.cs: ```csharp using System; using System.Linq; @@ -253,7 +254,8 @@ namespace OtripleS.Web.Api.Brokers.Storages return @object; } - private ValueTask> SelectAllAsync() where T : class => this.Set(); + private ValueTask> SelectAllAsync() where T : class => + await ValueTask.FromResult(this.Set()); private async ValueTask SelectAsync(params object[] @objectIds) where T : class => await this.FindAsync(objectIds); @@ -349,7 +351,7 @@ This responsibility lies in the hands of the broker-neighboring services. I also ## 1.7 FAQs Over time, some common questions arose from the engineers I worked with throughout my career. Since some of these questions reoccurred on several occasions, it might be helpful to aggregate them here so everyone can learn about other perspectives on brokers. -### 1.7.0 Is the Brokers Pattern the same as the Repository Pattern? +#### 1.7.0 Is the Brokers Pattern the same as the Repository Pattern? From an operational standpoint, brokers seem to be more generic than repositories. Repositories usually target storage-like operations, mainly towards databases. However, brokers can be an integration point for any external dependency, such as e-mail services, queues, and other APIs. @@ -360,27 +362,27 @@ In general, all these patterns try to implement the same SOLID principles: separ But because SOLID are principles and not exact guidelines, it's expected to see all different implementations and patterns to achieve that principle. -### 1.7.1 Why can't the brokers implement a contract for methods that return an interface instead of a concrete model? +#### 1.7.1 Why can't the brokers implement a contract for methods that return an interface instead of a concrete model? That would be an ideal situation, but that would also require brokers to do a conversion or mapping between the native models returned from the external resource SDKs or APIs and the internal model that adheres to the local contract. Doing that on the broker level will require introducing business logic into that realm, which is outside the purpose of that component. We define business logic code as any intended sequential, selective, or iteration code. Brokers are not unit-tested because they lack business logic. They may be part of an acceptance or integration test but certainly not part of unit-level tests simply because they do not contain business logic. -### 1.7.2 If brokers were a layer of abstraction from the business logic, why do we allow external exceptions to leak through them onto the services layer? +#### 1.7.2 If brokers were a layer of abstraction from the business logic, why do we allow external exceptions to leak through them onto the services layer? Brokers are only the *first* layer of abstraction, but not the only one. Broker-neighboring services are responsible for converting the native exceptions occurring in a broker into a more local exception model that can be handled and processed internally within the business logic realm. Business logic emerges in the processing, orchestration, coordination, and aggregation layers, where all the exceptions, returned models, and operations are local to the system. -### 1.7.3 Why do we use partial classes for brokers who handle multiple entities? +#### 1.7.3 Why do we use partial classes for brokers who handle multiple entities? Since brokers must own their configurations, it makes more sense to partialize when possible to avoid reconfiguring every storage broker for each entity. Partial classes are a feature in the C# language, but it should be possible to implement the same behavior through inheritance in other programming languages. -### 1.7.4 Are brokers the same as providers (Provider Pattern)? +#### 1.7.4 Are brokers the same as providers (Provider Pattern)? No. Providers blur the line between services (business logic) and brokers (integration layer). Brokers target particular disposable components within the system, but providers include more than just that. -### 1.7.5 Why is ValueTask preferred over Task in the implementations outlined in this section? +#### 1.7.5 Why is ValueTask preferred over Task in the implementations outlined in this section? ValueTask is a struct that is used to represent a task that returns a value. It is a value-based representation of a task that can be used to avoid allocations in the case where a task completes synchronously. @@ -388,28 +390,6 @@ In order to abstract at an operational level, all example implementations in thi This approach encourages a consistent handling of asynchronous operations across all method implementations, promoting a clean and efficient abstraction that aligns with the Standard principles. By using ValueTask, we reduce memory allocations in scenarios where the result is often available immediately, thereby enhancing performance while maintaining code clarity. -### 1.7.6 Why does or how can I supress warnings thrown in my code files? -Warnings are thrown by the compiler to alert you of potential issues in your code. It is important to address these warnings to ensure that your code is clean and free of potential bugs. -Engineers and developers can resolve warnings by following the suggestions provided by the compiler. This may involve making changes to the code, refactoring, or updating dependencies. - -It is important to regularly review and address warnings in your code to maintain code quality and ensure that your application runs smoothly. - -Here is one way to resolve warnings in your code: -To suppress a warning given by the IDE or compiler, you can configure your .csproj file using the element. -This approach allows you to disable specific warnings globally across your project. - -For example, adding CS1998 to a in your .csproj file will suppress the warning CS1998, which indicates an asynchronous method lacking await operators. If you need to suppress multiple warnings, you can list them separated by commas. This method provides a clean and consistent way to manage warnings without scattering #pragma warning directives throughout your code. -```csharp - - - - net8.0 - disable - disable - CS1998 - - ... -``` [*] [Implementing Abstract Components (Brokers)](https://www.youtube.com/watch?v=6NlgSskQXSo) diff --git a/2. Services/2.1 Foundations/2.1 Foundations.md b/2. Services/2.1 Foundations/2.1 Foundations.md index ad03457..c9cfb70 100644 --- a/2. Services/2.1 Foundations/2.1 Foundations.md +++ b/2. Services/2.1 Foundations/2.1 Foundations.md @@ -542,7 +542,7 @@ private async void ShouldThrowValidationExceptionOnAddWhenIdIsInvalidAndLogItAsy StudentValidationException actualStudentValidationException = await Assert.ThrowsAsync( - addStudentTask.AsTask); + registerStudentTask.AsTask); // then actualStudentValidationException.Should().BeEquivalentTo( @@ -759,12 +759,12 @@ private async Task ShouldThrowValidationExceptionOnAddIfUpdatedByNotSameAsCreate innerException: invalidStudentException); // when - ValueTask addStudentTask = + ValueTask registerStudentTask = this.studentService.AddStudentAsync(inputStudent); StudentValidationException actualStudentValidationException = await Assert.ThrowsAsync( - addStudentTask.AsTask); + registerStudentTask.AsTask); // then actualStudentValidationException.Should().BeEquivalentTo( @@ -1042,12 +1042,12 @@ private async void ShouldThrowDependencyValidationExceptionOnAddIfStudentAlready .ThrowsAsync(duplicateKeyException); // when - ValueTask addStudentTask = + ValueTask registerStudentTask = this.studentService.AddStudentAsync(inputStudent); StudentDependencyValidationException actualStudentDependencyValidationException = await Assert.ThrowsAsync( - addStudentTask.AsTask); + registerStudentTask.AsTask); // then actualStudentDependencyValidationException.Should().BeEquivalentTo(