CQRS & Mediator Part 2: Domain scaffolding with Roslyn API and Dotnet CLI

Armand Jordaan
ITNEXT
Published in
9 min readFeb 10, 2020

--

Waterfall at Jewel of Changi, Singapore

In part 2 we will be building a dotnet CLI tool which follows the CQRS and Mediator patterns to auto generate commands, queries, responses and handlers in the domain layer using Roslyn API for code generation. Git Repo

Problem

The most glaring challenge of the CQRS pattern is to overcome the number of classes a developer is required to create and can become cumbersome when constantly creating queries, commands, handlers, and response objects.

To solve this problem, this article will walk you through the process of creating a command-line scaffolding tool that can accept parameters such as the pattern type, concern, and operation. Resolve the paths and name of the domain in which directory we are executing the CLI command. Not only should it create the corresponding CQRS classes, but it will also add the class inheritance and implement the handle function of the IRequestHandler interface with the correct method signature.

This tool will enable less friction and context switching when creating the objects and perform the method wire ups correctly with the correct naming conventions, thus providing the developer with consistency across the domain and enable them to focus on the implementation rather than the abstraction.

In short. Enable developers to automate the repetitive and innovate in the unknown.

Tooling

If you are already familiar with the below concepts and tools skip ahead to the solution section for the code break down.

What is the Dotnet CLI?

The .NET Core command-line interface (CLI) is a cross-platform tool chain for developing .NET applications. The CLI is a foundation upon which higher-level tools, such as integrated development environments (IDEs), editors, and build orchestrators, can rest. — MSDN

Opinion: I prefer a UI over command line but the Dotnet CLI is a really powerful tool and yes for the Visual Studio users(myself included)the IDE is powerful with a rich feature set and advanced tooling support but at time of writing VS on windows still does not handle multi-project solutions all that well when it comes to load and build speeds — dies a little inside every time when opening a monolith with VS. :(

What is NuGet?

An essential tool for any modern development platform is a mechanism through which developers can create, share, and consume useful code. Often such code is bundled into “packages” that contain compiled code (as DLLs) along with other content needed in the projects that consume these packages. — MSDN

What is Roslyn?

It is the .NET compiler platform AKA Roslyn, it provides C#/ Visual basic, developers with access to the .Net compiler and rich code analysis APIs. Like most of the Dotnet world, this is open source and can be found at GitHub.

The API we will be working with is Microsoft.CodeAnalysis and Microsoft.CodeAnalysis.CSharp. Like most APIs/libraries, there is a learning curve and it is quite intimidating when you first start, however after looking at some implementation examples and classic RTFM it works like any other API having its concepts and ways of work.

Patterns

Fluent Interface Pattern

In short, it is a type of builder pattern that allows for the collection of data by specifying a list of functions on an object using method chaining.

This can be implemented as follows:

Usage:

Example of a class which implements the fluent interface pattern

Implementation:

Example of how to implement the fluent interface pattern

What stands out from this pattern is that you can use the interface return types to define specific logic paths, thus I’d recommend you to first define the interfaces and how the logic will flow and review each possibility.

For a more detailed break down of this pattern I recommend Dimitrie Tataru’s article on How to Design and Implement the Fluent Interface Pattern in C#

Usage

Installation:

dotnet tool install --global CQRSAndMediator.Scaffolding

Breakdown of concepts and commands:

Show help information:

scaffold -h

Command Parameters:

Concern: Name of the domain area you are working in i.e Orders, People, Invoice, etc.

-c|--concern <NAME>

Operation: Name of the action you are taking in your concern i.e GetById, GetPagedResult for queries or Create, Patch, Update for commands.

-c|--concern <NAME>

OperationType: The type of CQRS operation you are scaffolding i.e command or query

-ot|--operationtype <TYPE>

GroupBy: Specify how to group the domain actions. Either by concern or operation: Group domain objects by [C] for concerns or [O] for operations, defaults to concerns

-g|--groupBy <TYPE>

By concern: i.e group by concern when parameters are -c Orders -o GetById

-ot query

| YourDomainLayer
| Orders
| Handlers
| OrdersGetByIdHandler.cs
| Responses
| OrdersGetByIdResponse.cs
| Queries
| OrderGetByIdQuery.cs

By operation i.e group by the operation when parameters are -c Orders -o GetById -ot query -g O

| YourDomainLayer
| Handler
| OrdersGetByIdHandler.cs
| Responses
| OrdersGetByIdResponse.cs
| Queries
| OrderGetByIdQuery.cs

Note:
The tool requires that the project is set up already and that the actions are executed in the top-level directory of where your domain layer directory is located.

Example commands:
Use case scaffold out the CRUD domain for an invoice:

The create command when grouping by concern

scaffold -c Invoices -o Create -ot command
Scaffold the InvoicesCreateCommand

The get by id query when grouping by concern

scaffold -c Invoices -o GetById -ot query
Scaffold the OrdersGetByIdQuery

The GetPaged query when grouping by operation

scaffold -c Orders -o GetPaged -ot query -g o
Scaffold the OrdersGetPaged

The OrderCancelledCommand when grouping by operation

scaffold -c Orders -o Cancelled -ot command -g o
Scaffold the OrderCancelledCommand

Using CRUD operations is just an example without having to explain complexities and rules around a specific domain language. At the end of the day, the CQRS pattern doesn't care if it is a CRUD operation or a complex domain rule it all comes down to a command or a query.

Solution

We will be taking a look at the NuGet package’s project and how it has been implemented. The tool will be executed in Part 1’s solution structure thus will only focus on the output and not deep dive into the domain setup of the targeted solution’s domain layer. See part 1.

Structure:

I chose not to split the project into multiple layers of libraries, instead opted for a simpler subdirectory structure.

Dotnet CLI project structure
  • Program.cs: Application entry point.
  • Builders: Contains the implementation of the fluent interface pattern of the ClassAssembler.cs
  • Infrastructure: Contains the definition of the fluent interface pattern which wraps our implementation of the Roslyn API.
  • Models: Contains general object structures used across the application
  • Enums: Contains general types used across the application
  • nupkg: Contains the NuGet package information generated for each version and is uploaded to NuGet.org to update the package to the latest version — may automate the project build with Github Actions at a later date.

Code Deep Dive:

Startup.cs

Line 14–39: Initialization of command input parameters.

Line 43–77: Input parameter validation

Line 79: Invoke a response builder class to scaffold out the response directory and supporting classes in the targeted domain.

Line 81–95: Add logical split on Operation Type, then invoke the Query Builder object to scaffold out the query directory and supporting objects in the targeted domain.

Line 97: Invoke the Handler builder object to assemble and create the handler class for our targeted domain.

Note: Log is a static object containing Error(string) and Info(string) functions used throughout the application to standardize log messages.

Builders:

The idea of this layer is to abstract the Roslyn API implementation from its usage. All 4 builders have the same pattern.

BuildHandler.cs

The most complex use case of the ClassAssembler.cs, it is important to note that line 24–43 is where we make use of our fluent interface pattern. This is replicated to the other 3 builders each passing parameters to the ClassAssembler based on the use case.

Line 12–16: Logical split to build the name of the command/query object used on line 39.

Line 18–22: Logical split to build up the commands/queries namespace which needs to be added at the top of the handler class. Uses the utility function on line 46–51.

Line 25: Provide basic configuration details and the instantiation of the ClassAssember.

Line 26: Pass a list of Namespaces to add to the top of the Handler.cs class i.e import using directive as per Roslyn API.

Line 35: Creates the namespace declaration. A similar example would be equivalent to the line 6 but with the generated namespace name.

Line 36: Creates a class declaration.

Line 37: Specify which interfaces to inherit from.

Line 41: Instruct the ClassAssembler to implement the IRequestHandler’s handle function.

Line 42: Instructs the ClassAssembler to add the class metadata to the namespace and then adds the namespace metadata to Roslyn’s SyntaxFactory. Then convert the syntaxFactory node to a string, creates the directories as specified per the operation type and writes out the C# object to disk.

ClassAssembler:

Initialization:

Entrypoint and initialization of ClassAssembler.cs

Line 7: Called from the builders to start the process.

Line 1–5: The only constructor for the ClassAssembler.

Line 3: Creates/assigns a new instance of the DomainSettingsModel to the private _settings property. This DomainSettingsModel builds up all the directories and metadata about the targeted Domain’s projects absolute path, domain name, domain absolute path, etc.

Namespace addition and creation:

Namespace addition and creation extract from ClassAssembelr.cs

Line 1–9: Is responsible for adding/importing all the namespaces the class we are trying to create will use. i.e using System or using YourDomainName.Orders.Handlers.

Line 14–27: Will create the namespace for the current class which is being generated.

Class Creation:

Class Creation extract from ClassAssembler.cs

Line 4–5: Create a new public class.

Inheritance extract from ClassAssembler.cs

Line 8–9: Adds the types to inherit from the class declaration.

The ImplementMediatorHandler function is specific to the handler builder.

Line 16: Declare and add to the function’s syntax that it should throw a not implemented exception. This of course where you would add your handler’s implementation detail.

Line 17–21: Create an array that would contain the parameter list of the method signature along with the parameter name and its type.

Line 23–26: Declare a method named “Handle”, that is public, with our specified parameter list(17–21) and the method’s body syntax(line16).

Line 28: Add the method declaration to the class that we are building.

Generation extract from ClassAssembler.cs

Line 3: Add the generated class to the namespace.

Line 5: Add the namespace to the SyntaxFactory.

Line 7: Get Syntaxfactory as a string.

Line 11–23: Build up directory structures based on the operation type and grouping strategy.

Line 25: Write out the C# file to the directory with the string from line 7.

Line 28: Reset the _class, _namespace, and _syntaxfactory to null.

Conclusion

Building code generation tools with the Roslyn API is a dream, no more T4 template weirdness, and poor code editor support. — if you haven't worked with T4 templates consider yourself lucky. Should you find yourself building an API once in awhile using these patterns then this tool is just overkill, however when you need to output a high amount of features and build out various microservices that would benefit from these patterns then a code generation tool might assist greatly.

The future of this tool is not known yet. However, there is potential. Like pointing the scaffolding command to a DB entity and scaffold the entire CRUD operation right up to the stack to the controller layer, a CLI command to add a filter to a handler, or scaffold out the objects required to publish/subscribe from a message queue.

--

--

A dynamic and creative full stack Dotnet software developer with a passion for software development. https://armandjordaan.com