This is the first post. After studying, I wrote a code that adopted the idea of hexagonal architecture in Go.
I have referred to many sites, including Hexagonal Architecture Translation Site, but I am still not familiar with it and the description may be incorrect. If you notice any mistakes, please point them out.
A description of the hexagonal architecture can be found on this site (https://blog.tai2.net/hexagonal_architexture.html). It may be a little difficult to understand, so the features that I thought were important are listed below.
--Hexagonal architecture is an architecture that separates into two layers, the inner layer (adapter layer) and the outer layer (application layer). --The application (inside) and the external agent are separated by implementing communication with external agents such as HTTP, Email, and RDB in the outer adapter layer. --The adapter layer depends on the application layer, but the application layer must not depend on the adapter
The image looks like the figure below. (Hexagonal architecture is often represented by a hexagonal diagram as shown in the figure below.)
As mentioned above, the hexagonal architecture consists of two layers, the adapter layer and the application layer. Each layer has the following responsibilities:
-Implement interaction with external agents such as Email and RDB --The adapter layer depends on the application layer
--Application layer implements business logic --However, since the hexagonal architecture does not define business logic, it is up to the implementer how to implement it in the application layer. --Application layer must not depend on adapter layer
So far, we have explained the role of layers, but I think it is easier to imagine if you actually look at the code, so let's look at an implementation example.
The final configuration is Like this. Then, I will explain the implementation example for each layer.
The adapter layer implements interaction with external agents such as RDBs. Therefore, it has an implementation to acquire and create DB information.
The concrete implementation looks like the following.
As will be described later, define a structure for injecting into the interface of the application layer, and add a method to perform DB operations to that structure.
package dao
import (
"database/sql"
"hexagonal-architecture-sample/server/application/model"
"log"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
db *sql.DB
}
func ProveideUser(db *sql.DB) *User {
return &User{db: db}
}
//Since the adapter layer depends on the application layer, the model of the application layer is used.
func (u *User) GetByID(id string) (*model.User, error) {
var user model.User
result := u.db.QueryRow("select * from users where id=?", id)
err := result.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email)
if err != nil {
return nil, err
}
return &user, nil
}
At the application layer, you are responsible for implementing the business logic. However, the hexagonal architecture does not define business logic, so it is up to the implementer to implement it.
Also, in the hexagonal architecture, the application layer must not depend on the adapter layer. Therefore, you can avoid relying on the adapter layer by defining an interface that corresponds to the adapter layer in the application layer and injecting the adapter layer implementation into that interface.
The concrete implementation looks like the following.
package application
import (
"hexagonal-architecture-sample/server/application/model"
)
//The application layer defines the interface that corresponds to the adapter.
//You can avoid relying on the adapter layer by injecting an adapter implementation into this interface.
type User struct {
Interface repository.User
}
func (u *User) Create(user model.User) error {
return u.Interface.Create(user)
}
func (u *User) GetAll() ([]model.User, error) {
return u.Interface.GetAll()
}
func (u *User) Update(user model.User) error {
return u.Interface.Update(user)
}
func (u *User) GetByID(id string) (*model.User, error) {
return u.Interface.GetByID(id)
}
Application layer interface definition
package repository
import "hexagonal-architecture-sample/server/application/model"
type User interface {
Create(user model.User) error
GetAll() ([]model.User, error)
Update(user model.User) error
GetByID(id string) (*model.User, error)
}
Make the application layer independent of the adapter by injecting the adapter layer implementation into the interface defined in the application layer with the main function.
package main
import (
"hexagonal-architecture-sample/server/adapter/mysql"
"hexagonal-architecture-sample/server/adapter/mysql/dao"
"hexagonal-architecture-sample/server/adapter/router"
"hexagonal-architecture-sample/server/application"
"log"
"net/http"
)
func main() {
resources := mysql.NewResource()
resources.Initialize()
defer resources.Finalize()
p := &router.Provide{
User: router.User{
//I'm injecting an adapter layer User structure into the application layer interface.
User: application.User{
Interface: dao.ProveideUser(resources.DB),
},
},
}
err := http.ListenAndServe(":8080", router.NewRouter(resources, *p))
if err != nil {
log.Fatal("ListenAndServe", err)
}
}
I didn't know what to do with the directory structure because there is no full stack framework in Go language, so I learned about hexagonal architecture this time. I think that the hexagonal architecture will have a cohesive directory structure by dropping the layer structure into the directory as it is. If you don't know the best practices for directory structure in Go, learning about architecture can help.
That is all for the content of the article.
Hexagonal Architecture (https://blog.tai2.net/hexagonal_architexture.html#id5) Hexagonal architecture in Go Pospome server-side architecture What is the most accessible architecture to get started with Domain Driven Design [DDD]
Recommended Posts