From e6732d7611223b6e552d736775191e417bb07c45 Mon Sep 17 00:00:00 2001 From: duykhoa Date: Mon, 21 Oct 2024 04:06:03 +0000 Subject: [PATCH] deploy: 6f20f16777e79796cb3ddb3a0b5acec3fa4df641 --- index.xml | 23 +++++++++++++++++--- posts/command-query-in-go-project/index.html | 11 ++++++---- posts/index.xml | 23 +++++++++++++++++--- tags/architecture/index.xml | 23 +++++++++++++++++--- tags/domain-driven-development/index.xml | 23 +++++++++++++++++--- tags/golang/index.xml | 23 +++++++++++++++++--- 6 files changed, 107 insertions(+), 19 deletions(-) diff --git a/index.xml b/index.xml index 884d979..b4fc60e 100644 --- a/index.xml +++ b/index.xml @@ -113,8 +113,6 @@ To provide a clear picture, here is a pattern I am using when defind the command
baseHandler := createArticleHandler{}
 
-createArticleHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
-
 func DecorateCommand[H any](handler CommandHandler[H], logger: logger) {
 	return commandLogging {
 		base:   handler,
@@ -145,7 +143,26 @@ To provide a clear picture, here is a pattern I am using when defind the command
 	}()
 
 	return d.base.Handle(ctx, cmd)
-}

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

+}

The commandLogging produces the logs when running the command handler. It adds a log entry before calling the handler Handle function, and depending on the Handle function’s result, it will produce success or failure log entry. The commandLogging also implements the CommandHandler interface, the consumer doesn’t require to change.

+

Let’s assume the application expects the GetArticleHandler as a dependency, the application could be initialized as follow

+ + + + + +
	func NewApplication(cmdHandler GetArticleHandler) Application {...}
+	cmdHandler := createArticleHandler{}
+
+	app := NewApplication(cmdHandler)

Using the DecorateCommand function to add the logging functionality, the code is changed to

+ + + + + +
	wrappedHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
+
+	app := NewApplication(cmdHandler)

The application layer remains unaffected by the wrapped GetArticleHandler. The app’s behavior remains unchanged, requiring no modifications to implement this setup.

+

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. This allows for the application of multiple decorators to the original handler, each providing additional functionalities while keeping the original handler agnostic to these modifications.

Recap

diff --git a/posts/command-query-in-go-project/index.html b/posts/command-query-in-go-project/index.html index 0ed3755..58bf325 100644 --- a/posts/command-query-in-go-project/index.html +++ b/posts/command-query-in-go-project/index.html @@ -3,7 +3,7 @@ Command Query Separation (CQS) is a design pattern principle that defines the incoming request handler type, which is either modify the application state - commands, or retrieving data without any side effect - queries. By applying CQS to service objects, we can create clean interface. This blog post will explore further the benefits of CQS and how to create command and query objects in existing application.">
+Command Query Separation (CQS) is a design pattern principle that defines the incoming request handler type, which is either modify the application state - commands, or retrieving data without any side effect - queries. By applying CQS to service objects, we can create clean interface. This blog post will explore further the benefits of CQS and how to create command and query objects in existing application.">

Kevin's Blog

Command Query in Go Project

In Model-View-Controller applications, a common practice is to introduce service objects to encapsulate business logic and manage the interaction to external systems. While service objects are useful abstraction, further structuring them could improve the code organization and maintainability.

Command Query Separation (CQS) is a design pattern principle that defines the incoming request handler type, which is either modify the application state - commands, or retrieving data without any side effect - queries. By applying CQS to service objects, we can create clean interface. This blog post will explore further the benefits of CQS and how to create command and query objects in existing application.

Service Object’s challenges

When using service objects, the application architecture is structured as follows

services

Anything could be thrown to the service layer, which is a good convenience at first. When the project grows, the service layer also grows and become complicated to maintain. Take a look at this services diagram

services dependencies

The service layer can quickly become bloated since any type of business logic can be encapsulated within. When start working on a new feature, developers often plan to create new service object if no existing service. The more features developed, the higher number of service objects added, which makes the system more complex and difficult to maintain.

In addition, service objects can introduce dependencies on one another, creating a tangled relationship between services. This adds more difficulty to understand and modify, as changes one service may have unintended consequences to others.

An effort could be made to unify the service’s interfaces, however, this is going to be a challenging task that developers may overlook. A standardized interface is introduced early in the project, it may become technical debt that is abandoned when the requirements are evolved.

Command Query Separation

To improve maintainability, testability and code readability, the service object could be categorized either as command object, which modifies the application’s state, or query object, which returning data without side effects.

By using this definition, an interface could be introduced for all Command handlers.

type CommandHandler[C any] interface {
@@ -58,8 +58,6 @@
 By focusing in a single aspect, the test scenario is more comprehend to build and maintain.

The command and query implements the generic command handler and query handler interface, additional functionalities such as logging could be implemented without touching to the command/query handler. To provide a clear picture, here is a pattern I am using when defind the command handler. The query decorators could be implemented with similar approach.

baseHandler := createArticleHandler{}
 
-createArticleHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
-
 func DecorateCommand[H any](handler CommandHandler[H], logger: logger) {
 	return commandLogging {
 		base:   handler,
@@ -90,7 +88,12 @@
 	}()
 
 	return d.base.Handle(ctx, cmd)
-}

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. +}

The commandLogging produces the logs when running the command handler. It adds a log entry before calling the handler Handle function, and depending on the Handle function’s result, it will produce success or failure log entry. The commandLogging also implements the CommandHandler interface, the consumer doesn’t require to change.

Let’s assume the application expects the GetArticleHandler as a dependency, the application could be initialized as follow

	func NewApplication(cmdHandler GetArticleHandler) Application {...}
+	cmdHandler := createArticleHandler{}
+
+	app := NewApplication(cmdHandler)

Using the DecorateCommand function to add the logging functionality, the code is changed to

	wrappedHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
+
+	app := NewApplication(cmdHandler)

The application layer remains unaffected by the wrapped GetArticleHandler. The app’s behavior remains unchanged, requiring no modifications to implement this setup.

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. This allows for the application of multiple decorators to the original handler, each providing additional functionalities while keeping the original handler agnostic to these modifications.

Recap

CQS enhances the modularity and maintainability of the application by separating commands (which modify the system state) from queries (which retrieve information). This alignment with the Domain-Driven Design (DDD) paradigm, which favors the Command-Query Separation (CQS) pattern, makes the transition to a DDD-based architecture more seamless.

#architecture #golang diff --git a/posts/index.xml b/posts/index.xml index 0075e71..a1f9bf4 100644 --- a/posts/index.xml +++ b/posts/index.xml @@ -101,8 +101,6 @@ To provide a clear picture, here is a pattern I am using when defind the command

baseHandler := createArticleHandler{}
 
-createArticleHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
-
 func DecorateCommand[H any](handler CommandHandler[H], logger: logger) {
 	return commandLogging {
 		base:   handler,
@@ -133,7 +131,26 @@ To provide a clear picture, here is a pattern I am using when defind the command
 	}()
 
 	return d.base.Handle(ctx, cmd)
-}

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

+}

The commandLogging produces the logs when running the command handler. It adds a log entry before calling the handler Handle function, and depending on the Handle function’s result, it will produce success or failure log entry. The commandLogging also implements the CommandHandler interface, the consumer doesn’t require to change.

+

Let’s assume the application expects the GetArticleHandler as a dependency, the application could be initialized as follow

+ + + + + +
	func NewApplication(cmdHandler GetArticleHandler) Application {...}
+	cmdHandler := createArticleHandler{}
+
+	app := NewApplication(cmdHandler)

Using the DecorateCommand function to add the logging functionality, the code is changed to

+ + + + + +
	wrappedHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
+
+	app := NewApplication(cmdHandler)

The application layer remains unaffected by the wrapped GetArticleHandler. The app’s behavior remains unchanged, requiring no modifications to implement this setup.

+

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. This allows for the application of multiple decorators to the original handler, each providing additional functionalities while keeping the original handler agnostic to these modifications.

Recap

diff --git a/tags/architecture/index.xml b/tags/architecture/index.xml index 4d5b021..5057d52 100644 --- a/tags/architecture/index.xml +++ b/tags/architecture/index.xml @@ -101,8 +101,6 @@ To provide a clear picture, here is a pattern I am using when defind the command
baseHandler := createArticleHandler{}
 
-createArticleHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
-
 func DecorateCommand[H any](handler CommandHandler[H], logger: logger) {
 	return commandLogging {
 		base:   handler,
@@ -133,7 +131,26 @@ To provide a clear picture, here is a pattern I am using when defind the command
 	}()
 
 	return d.base.Handle(ctx, cmd)
-}

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

+}

The commandLogging produces the logs when running the command handler. It adds a log entry before calling the handler Handle function, and depending on the Handle function’s result, it will produce success or failure log entry. The commandLogging also implements the CommandHandler interface, the consumer doesn’t require to change.

+

Let’s assume the application expects the GetArticleHandler as a dependency, the application could be initialized as follow

+ + + + + +
	func NewApplication(cmdHandler GetArticleHandler) Application {...}
+	cmdHandler := createArticleHandler{}
+
+	app := NewApplication(cmdHandler)

Using the DecorateCommand function to add the logging functionality, the code is changed to

+ + + + + +
	wrappedHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
+
+	app := NewApplication(cmdHandler)

The application layer remains unaffected by the wrapped GetArticleHandler. The app’s behavior remains unchanged, requiring no modifications to implement this setup.

+

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. This allows for the application of multiple decorators to the original handler, each providing additional functionalities while keeping the original handler agnostic to these modifications.

Recap

diff --git a/tags/domain-driven-development/index.xml b/tags/domain-driven-development/index.xml index be8fd77..dd516dc 100644 --- a/tags/domain-driven-development/index.xml +++ b/tags/domain-driven-development/index.xml @@ -101,8 +101,6 @@ To provide a clear picture, here is a pattern I am using when defind the command
baseHandler := createArticleHandler{}
 
-createArticleHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
-
 func DecorateCommand[H any](handler CommandHandler[H], logger: logger) {
 	return commandLogging {
 		base:   handler,
@@ -133,7 +131,26 @@ To provide a clear picture, here is a pattern I am using when defind the command
 	}()
 
 	return d.base.Handle(ctx, cmd)
-}

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

+}

The commandLogging produces the logs when running the command handler. It adds a log entry before calling the handler Handle function, and depending on the Handle function’s result, it will produce success or failure log entry. The commandLogging also implements the CommandHandler interface, the consumer doesn’t require to change.

+

Let’s assume the application expects the GetArticleHandler as a dependency, the application could be initialized as follow

+ + + + + +
	func NewApplication(cmdHandler GetArticleHandler) Application {...}
+	cmdHandler := createArticleHandler{}
+
+	app := NewApplication(cmdHandler)

Using the DecorateCommand function to add the logging functionality, the code is changed to

+ + + + + +
	wrappedHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
+
+	app := NewApplication(cmdHandler)

The application layer remains unaffected by the wrapped GetArticleHandler. The app’s behavior remains unchanged, requiring no modifications to implement this setup.

+

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. This allows for the application of multiple decorators to the original handler, each providing additional functionalities while keeping the original handler agnostic to these modifications.

Recap

diff --git a/tags/golang/index.xml b/tags/golang/index.xml index 2df1d3b..79152c7 100644 --- a/tags/golang/index.xml +++ b/tags/golang/index.xml @@ -101,8 +101,6 @@ To provide a clear picture, here is a pattern I am using when defind the command
baseHandler := createArticleHandler{}
 
-createArticleHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
-
 func DecorateCommand[H any](handler CommandHandler[H], logger: logger) {
 	return commandLogging {
 		base:   handler,
@@ -133,7 +131,26 @@ To provide a clear picture, here is a pattern I am using when defind the command
 	}()
 
 	return d.base.Handle(ctx, cmd)
-}

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

+}

The commandLogging produces the logs when running the command handler. It adds a log entry before calling the handler Handle function, and depending on the Handle function’s result, it will produce success or failure log entry. The commandLogging also implements the CommandHandler interface, the consumer doesn’t require to change.

+

Let’s assume the application expects the GetArticleHandler as a dependency, the application could be initialized as follow

+ + + + + +
	func NewApplication(cmdHandler GetArticleHandler) Application {...}
+	cmdHandler := createArticleHandler{}
+
+	app := NewApplication(cmdHandler)

Using the DecorateCommand function to add the logging functionality, the code is changed to

+ + + + + +
	wrappedHandler := DecorateCommand(baseHandler, zerolog.New(os.StdErr))
+
+	app := NewApplication(cmdHandler)

The application layer remains unaffected by the wrapped GetArticleHandler. The app’s behavior remains unchanged, requiring no modifications to implement this setup.

+

This setup adheres to the open for extension, closed for modification principle, enabling the addition of logging functionality without altering the createArticleHandler function directly.

The DecorateCommand function applies the decorators, wrapping the original handler with a commandLogging struct. This allows for the application of multiple decorators to the original handler, each providing additional functionalities while keeping the original handler agnostic to these modifications.

Recap