├── .go-version ├── .gitignore ├── ddd ├── 02entity │ ├── go.mod │ ├── domain │ │ ├── entity │ │ │ └── user.go │ │ └── value-object │ │ │ └── user.go │ └── main.go ├── 01value-object │ ├── go.mod │ ├── domain │ │ └── value-object │ │ │ ├── fullname.go │ │ │ └── money.go │ └── main.go ├── 04repository │ ├── go.mod │ ├── domain │ │ ├── domain-service │ │ │ └── user.go │ │ ├── entity │ │ │ └── user.go │ │ ├── repository │ │ │ └── user.go │ │ └── value-object │ │ │ └── user.go │ └── main.go ├── 05application │ ├── go.mod │ ├── domain │ │ ├── domain-service │ │ │ └── user.go │ │ ├── entity │ │ │ └── user.go │ │ ├── value-object │ │ │ └── user.go │ │ ├── application │ │ │ └── user.go │ │ └── repository │ │ │ └── user.go │ └── main.go └── 03domain-service │ ├── go.mod │ ├── domain │ ├── domain-service │ │ └── user.go │ ├── entity │ │ └── user.go │ └── value-object │ │ └── user.go │ └── main.go ├── clean-architecture ├── migrations │ ├── 000001_create_todos.down.sql │ └── 000001_create_todos.up.sql ├── interface │ ├── controller │ │ ├── context.go │ │ ├── error.go │ │ └── todo.go │ ├── presenter │ │ └── todo.go │ └── repository │ │ └── todo.go ├── main.go ├── domain │ ├── application │ │ ├── usecase │ │ │ ├── todo_input_port.go │ │ │ ├── todo_output_port.go │ │ │ └── todo_interactor.go │ │ └── repository │ │ │ └── todo.go │ └── entity │ │ └── todo.go ├── infrastructure │ └── router │ │ ├── api │ │ └── todo.go │ │ └── router.go ├── go.mod ├── readme.md └── go.sum ├── gin-samples ├── 06todoapp │ ├── migrations │ │ ├── 000001_create_todos.down.sql │ │ └── 000001_create_todos.up.sql │ ├── conf │ │ ├── development.ini │ │ └── test.ini │ ├── main.go │ ├── routers │ │ ├── router.go │ │ ├── router_test.go │ │ └── api │ │ │ └── todo.go │ ├── models │ │ ├── models.go │ │ └── todo.go │ ├── go.mod │ └── go.sum ├── 05orm │ ├── go.mod │ ├── go.sum │ └── main.go ├── 04test │ ├── request_test.go │ ├── tabledriven_test.go │ ├── go.mod │ └── go.sum ├── 03bind │ ├── go.mod │ ├── main.go │ └── go.sum ├── 01sample │ ├── go.mod │ ├── main.go │ └── go.sum └── 02middleware │ ├── go.mod │ ├── main.go │ └── go.sum ├── docs ├── part3_usecase.png ├── part0.md ├── part3.md ├── part4.md ├── part3_domain.svg └── part1.md ├── readme.md └── docker-compose.yml /.go-version: -------------------------------------------------------------------------------- 1 | 1.17.7 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/bin/* 2 | **/pkg/* 3 | -------------------------------------------------------------------------------- /ddd/02entity/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /ddd/01value-object/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /ddd/04repository/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /ddd/05application/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /ddd/03domain-service/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /clean-architecture/migrations/000001_create_todos.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `todos`; -------------------------------------------------------------------------------- /gin-samples/06todoapp/migrations/000001_create_todos.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `todos`; -------------------------------------------------------------------------------- /docs/part3_usecase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arakawamoriyuki/go-clean-handson/HEAD/docs/part3_usecase.png -------------------------------------------------------------------------------- /gin-samples/06todoapp/conf/development.ini: -------------------------------------------------------------------------------- 1 | [database] 2 | Type = mysql 3 | User = root 4 | Password = pass 5 | Host = localhost 6 | Port = 3306 7 | Name = gin-sample 8 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/conf/test.ini: -------------------------------------------------------------------------------- 1 | [database] 2 | Type = mysql 3 | User = root 4 | Password = pass 5 | Host = localhost 6 | Port = 3306 7 | Name = gin-sample-test 8 | -------------------------------------------------------------------------------- /clean-architecture/interface/controller/context.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | type Context interface { 4 | Param(key string) string 5 | JSON(code int, obj interface{}) 6 | Abort() 7 | } 8 | -------------------------------------------------------------------------------- /clean-architecture/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/infrastructure/router" 5 | ) 6 | 7 | func main() { 8 | r := router.SetupRouter() 9 | r.Run(":8080") 10 | } 11 | -------------------------------------------------------------------------------- /ddd/03domain-service/domain/domain-service/user.go: -------------------------------------------------------------------------------- 1 | package domainservice 2 | 3 | import ( 4 | entity "main/domain/entity" 5 | ) 6 | 7 | func UserExists(user entity.User) bool { 8 | 9 | // TODO: 重複確認処理 10 | exists := false 11 | 12 | return exists 13 | } 14 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Goで理解する Clean Architecture 入門 2 | 3 | Clean Architectureを理解するのに必要な知識を順を追って解説 4 | 5 | - [環境構築](./docs/part0.md) 6 | - [Go 入門](./docs/part1.md) 7 | - [Gin 入門](./docs/part2.md) 8 | - [DDD 入門](./docs/part3.md) 9 | - [Clean Architecture 入門](./docs/part4.md) 10 | -------------------------------------------------------------------------------- /clean-architecture/interface/controller/error.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | type ErrorResponse struct { 4 | Message string `json:"message"` 5 | } 6 | 7 | func NewErrorResponse(message string) *ErrorResponse { 8 | return &ErrorResponse{ 9 | Message: message, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /clean-architecture/domain/application/usecase/todo_input_port.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | // Input Data 4 | type TodoInputData struct { 5 | Id int 6 | } 7 | 8 | // Input Boundary 9 | type TodoInputPortInterface interface { 10 | Get(TodoInputData) (*TodoResponse, error) 11 | } 12 | -------------------------------------------------------------------------------- /clean-architecture/domain/entity/todo.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | // Entities 4 | type TodoInterface interface { 5 | } 6 | 7 | func NewTodo() TodoInterface { 8 | return &Todo{} 9 | } 10 | 11 | type Todo struct { 12 | Id int 13 | Name string 14 | } 15 | 16 | type TodoForm struct { 17 | Name string 18 | } 19 | -------------------------------------------------------------------------------- /clean-architecture/infrastructure/router/api/todo.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "main/interface/controller" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func GetTodoHandler(ctrl *controller.TodoController) gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | ctrl.GetTodo(c) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "main/models" 5 | "main/pkg/setting" 6 | "main/routers" 7 | ) 8 | 9 | func init() { 10 | setting.Setup("conf/development.ini") 11 | models.Setup() 12 | } 13 | 14 | func main() { 15 | r := routers.SetupRouter() 16 | r.Run(":8080") 17 | } 18 | -------------------------------------------------------------------------------- /gin-samples/05orm/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require ( 6 | gorm.io/driver/mysql v1.3.2 7 | gorm.io/gorm v1.23.3 8 | ) 9 | 10 | require ( 11 | github.com/go-sql-driver/mysql v1.6.0 // indirect 12 | github.com/jinzhu/inflection v1.0.0 // indirect 13 | github.com/jinzhu/now v1.1.4 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /ddd/04repository/domain/domain-service/user.go: -------------------------------------------------------------------------------- 1 | package domainservice 2 | 3 | import ( 4 | entity "main/domain/entity" 5 | repository "main/domain/repository" 6 | ) 7 | 8 | func UserExists(user entity.User) bool { 9 | userRepository := repository.NewUserRepository() 10 | 11 | exists := userRepository.Exists(user) 12 | 13 | return exists 14 | } 15 | -------------------------------------------------------------------------------- /ddd/05application/domain/domain-service/user.go: -------------------------------------------------------------------------------- 1 | package domainservice 2 | 3 | import ( 4 | entity "main/domain/entity" 5 | repository "main/domain/repository" 6 | ) 7 | 8 | func UserExists(user entity.User) bool { 9 | userRepository := repository.NewUserRepository() 10 | 11 | exists := userRepository.Exists(user) 12 | 13 | return exists 14 | } 15 | -------------------------------------------------------------------------------- /clean-architecture/migrations/000001_create_todos.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `todos` ( 2 | `id` BIGINT(20) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 | `name` VARCHAR(256) NOT NULL, 4 | `done` BOOLEAN NOT NULL, 5 | `created_at` DATETIME NOT NULL, 6 | `updated_at` DATETIME NOT NULL, 7 | `deleted_at` DATETIME 8 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; 9 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/migrations/000001_create_todos.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `todos` ( 2 | `id` BIGINT(20) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 3 | `name` VARCHAR(256) NOT NULL, 4 | `done` BOOLEAN NOT NULL, 5 | `created_at` DATETIME NOT NULL, 6 | `updated_at` DATETIME NOT NULL, 7 | `deleted_at` DATETIME 8 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | db: 5 | image: mysql:8.0.28 6 | environment: 7 | MYSQL_DATABASE: gin-sample 8 | MYSQL_ROOT_PASSWORD: pass 9 | ports: 10 | - 3306:3306 11 | volumes: 12 | - mysql-data:/var/lib/mysql 13 | command: mysqld --default-authentication-plugin=mysql_native_password 14 | 15 | volumes: 16 | mysql-data: 17 | driver: local 18 | -------------------------------------------------------------------------------- /clean-architecture/domain/application/usecase/todo_output_port.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | // Output Data 4 | type TodoOutputData struct { 5 | Id int 6 | Name string 7 | } 8 | 9 | type TodoResponse struct { 10 | Id int `json:"id"` 11 | Name string `json:"name"` 12 | } 13 | 14 | // Output Boundary 15 | type TodoOutputPortInterface interface { 16 | Convert(TodoOutputData) (*TodoResponse, error) 17 | } 18 | -------------------------------------------------------------------------------- /clean-architecture/domain/application/repository/todo.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | entiry "main/domain/entity" 5 | ) 6 | 7 | // Data Access Interface 8 | type TodoRepositoryInterface interface { 9 | // GetList() ([]*entiry.Todo, error) 10 | Get(int) (*entiry.Todo, error) 11 | // Create(*entiry.TodoForm) (*entiry.Todo, error) 12 | // Update(*entiry.Todo) (*entiry.Todo, error) 13 | // Delete(entiry.TodoId) error 14 | } 15 | -------------------------------------------------------------------------------- /ddd/01value-object/domain/value-object/fullname.go: -------------------------------------------------------------------------------- 1 | package valueobject 2 | 3 | type FullName struct { 4 | firstName string 5 | lastName string 6 | } 7 | 8 | func NewFullName(firstName string, lastName string) *FullName { 9 | fullName := &FullName{ 10 | firstName: firstName, 11 | lastName: lastName, 12 | } 13 | return fullName 14 | } 15 | 16 | func (f FullName) Equals(fullName *FullName) bool { 17 | return f.firstName == fullName.firstName && f.lastName == fullName.lastName 18 | } 19 | -------------------------------------------------------------------------------- /ddd/05application/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | application "main/domain/application" 6 | repository "main/domain/repository" 7 | ) 8 | 9 | func main() { 10 | repo := repository.NewUserRepository() 11 | app := application.NewUserApplication(repo) 12 | 13 | user, err := app.FindUser("Moriyuki Arakawa") 14 | if err != nil { 15 | fmt.Println(err) 16 | return 17 | } 18 | 19 | log := fmt.Sprintf("FindUser id:%d name:%s", user.Id, user.Name) 20 | fmt.Println(log) 21 | } 22 | -------------------------------------------------------------------------------- /ddd/02entity/domain/entity/user.go: -------------------------------------------------------------------------------- 1 | package entiry 2 | 3 | import ( 4 | vo "main/domain/value-object" 5 | ) 6 | 7 | type User struct { 8 | Id vo.UserId 9 | Name vo.UserName 10 | } 11 | 12 | func NewUser(id vo.UserId, name vo.UserName) *User { 13 | user := &User{ 14 | Id: id, 15 | Name: name, 16 | } 17 | return user 18 | } 19 | 20 | func (u User) Equals(user *User) bool { 21 | return u.Id == user.Id 22 | } 23 | 24 | func (u *User) ChangeName(name vo.UserName) { 25 | u.Name = name 26 | } 27 | -------------------------------------------------------------------------------- /ddd/04repository/domain/entity/user.go: -------------------------------------------------------------------------------- 1 | package entiry 2 | 3 | import ( 4 | vo "main/domain/value-object" 5 | ) 6 | 7 | type User struct { 8 | Id vo.UserId 9 | Name vo.UserName 10 | } 11 | 12 | func NewUser(id vo.UserId, name vo.UserName) *User { 13 | user := &User{ 14 | Id: id, 15 | Name: name, 16 | } 17 | return user 18 | } 19 | 20 | func (u User) Equals(user *User) bool { 21 | return u.Id == user.Id 22 | } 23 | 24 | func (u *User) ChangeName(name vo.UserName) { 25 | u.Name = name 26 | } 27 | -------------------------------------------------------------------------------- /clean-architecture/interface/presenter/todo.go: -------------------------------------------------------------------------------- 1 | package presenter 2 | 3 | import ( 4 | "main/domain/application/usecase" 5 | ) 6 | 7 | // Presenter 8 | type TodoPresenter struct { 9 | } 10 | 11 | func NewTodoPresenter() usecase.TodoOutputPortInterface { 12 | return &TodoPresenter{} 13 | } 14 | 15 | func (p *TodoPresenter) Convert(outputData usecase.TodoOutputData) (*usecase.TodoResponse, error) { 16 | return &usecase.TodoResponse{ 17 | Id: outputData.Id, 18 | Name: outputData.Name, 19 | }, nil 20 | } 21 | -------------------------------------------------------------------------------- /ddd/03domain-service/domain/entity/user.go: -------------------------------------------------------------------------------- 1 | package entiry 2 | 3 | import ( 4 | vo "main/domain/value-object" 5 | ) 6 | 7 | type User struct { 8 | Id vo.UserId 9 | Name vo.UserName 10 | } 11 | 12 | func NewUser(id vo.UserId, name vo.UserName) *User { 13 | user := &User{ 14 | Id: id, 15 | Name: name, 16 | } 17 | return user 18 | } 19 | 20 | func (u User) Equals(user *User) bool { 21 | return u.Id == user.Id 22 | } 23 | 24 | func (u *User) ChangeName(name vo.UserName) { 25 | u.Name = name 26 | } 27 | -------------------------------------------------------------------------------- /ddd/05application/domain/entity/user.go: -------------------------------------------------------------------------------- 1 | package entiry 2 | 3 | import ( 4 | vo "main/domain/value-object" 5 | ) 6 | 7 | type User struct { 8 | Id vo.UserId 9 | Name vo.UserName 10 | } 11 | 12 | func NewUser(id vo.UserId, name vo.UserName) *User { 13 | user := &User{ 14 | Id: id, 15 | Name: name, 16 | } 17 | return user 18 | } 19 | 20 | func (u User) Equals(user *User) bool { 21 | return u.Id == user.Id 22 | } 23 | 24 | func (u *User) ChangeName(name vo.UserName) { 25 | u.Name = name 26 | } 27 | -------------------------------------------------------------------------------- /ddd/04repository/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ds "main/domain/domain-service" 6 | entity "main/domain/entity" 7 | vo "main/domain/value-object" 8 | ) 9 | 10 | func main() { 11 | userId, err := vo.NewUserId(1) 12 | if err != nil { 13 | fmt.Println(err) 14 | } 15 | userName, err := vo.NewUserName("新川 盛幸") 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | user := entity.NewUser(*userId, *userName) 20 | 21 | log := fmt.Sprintf("Exists: %t", ds.UserExists(*user)) 22 | fmt.Println(log) 23 | } 24 | -------------------------------------------------------------------------------- /ddd/03domain-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ds "main/domain/domain-service" 6 | entity "main/domain/entity" 7 | vo "main/domain/value-object" 8 | ) 9 | 10 | func main() { 11 | userId, err := vo.NewUserId(1) 12 | if err != nil { 13 | fmt.Println(err) 14 | } 15 | userName, err := vo.NewUserName("新川 盛幸") 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | user := entity.NewUser(*userId, *userName) 20 | 21 | log := fmt.Sprintf("Exists: %t", ds.UserExists(*user)) 22 | fmt.Println(log) 23 | } 24 | -------------------------------------------------------------------------------- /ddd/04repository/domain/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | entity "main/domain/entity" 5 | ) 6 | 7 | type UserRepositoryInterface interface { 8 | Exists(user entity.User) bool 9 | } 10 | 11 | type UserRepository struct{} 12 | 13 | func NewUserRepository() UserRepositoryInterface { 14 | repository := &UserRepository{} 15 | return repository 16 | } 17 | 18 | func (r *UserRepository) Exists(user entity.User) bool { 19 | 20 | // TODO: DBからuser.Idのレコードがあるか確認する処理 21 | exists := false 22 | 23 | return exists 24 | } 25 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | 6 | "main/routers/api" 7 | ) 8 | 9 | func SetupRouter() *gin.Engine { 10 | router := gin.Default() 11 | apiGroup := router.Group("api") 12 | { 13 | todosGroup := apiGroup.Group("todos") 14 | { 15 | todosGroup.GET("", api.GetTodos) 16 | todosGroup.GET(":id", api.GetTodo) 17 | todosGroup.POST("", api.CreateTodo) 18 | todosGroup.PATCH(":id", api.UpdateTodo) 19 | todosGroup.DELETE(":id", api.DeleteTodo) 20 | } 21 | } 22 | 23 | return router 24 | } 25 | -------------------------------------------------------------------------------- /ddd/02entity/domain/value-object/user.go: -------------------------------------------------------------------------------- 1 | package valueobject 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type UserId struct { 8 | value int 9 | } 10 | 11 | func NewUserId(id int) (*UserId, error) { 12 | if id < 1 { 13 | return nil, errors.New("ユーザーIDは1以上である必要があります") 14 | } 15 | 16 | userId := &UserId{ 17 | value: id, 18 | } 19 | return userId, nil 20 | } 21 | 22 | type UserName struct { 23 | value string 24 | } 25 | 26 | func NewUserName(name string) (*UserName, error) { 27 | if len(name) < 3 { 28 | return nil, errors.New("ユーザー名は3文字以上である必要があります") 29 | } 30 | 31 | userName := &UserName{ 32 | value: name, 33 | } 34 | return userName, nil 35 | } 36 | -------------------------------------------------------------------------------- /ddd/04repository/domain/value-object/user.go: -------------------------------------------------------------------------------- 1 | package valueobject 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type UserId struct { 8 | value int 9 | } 10 | 11 | func NewUserId(id int) (*UserId, error) { 12 | if id < 1 { 13 | return nil, errors.New("ユーザーIDは1以上である必要があります") 14 | } 15 | 16 | userId := &UserId{ 17 | value: id, 18 | } 19 | return userId, nil 20 | } 21 | 22 | type UserName struct { 23 | value string 24 | } 25 | 26 | func NewUserName(name string) (*UserName, error) { 27 | if len(name) < 3 { 28 | return nil, errors.New("ユーザー名は3文字以上である必要があります") 29 | } 30 | 31 | userName := &UserName{ 32 | value: name, 33 | } 34 | return userName, nil 35 | } 36 | -------------------------------------------------------------------------------- /ddd/05application/domain/value-object/user.go: -------------------------------------------------------------------------------- 1 | package valueobject 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type UserId struct { 8 | value int 9 | } 10 | 11 | func NewUserId(id int) (*UserId, error) { 12 | if id < 1 { 13 | return nil, errors.New("ユーザーIDは1以上である必要があります") 14 | } 15 | 16 | userId := &UserId{ 17 | value: id, 18 | } 19 | return userId, nil 20 | } 21 | 22 | type UserName struct { 23 | value string 24 | } 25 | 26 | func NewUserName(name string) (*UserName, error) { 27 | if len(name) < 3 { 28 | return nil, errors.New("ユーザー名は3文字以上である必要があります") 29 | } 30 | 31 | userName := &UserName{ 32 | value: name, 33 | } 34 | return userName, nil 35 | } 36 | -------------------------------------------------------------------------------- /ddd/03domain-service/domain/value-object/user.go: -------------------------------------------------------------------------------- 1 | package valueobject 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type UserId struct { 8 | value int 9 | } 10 | 11 | func NewUserId(id int) (*UserId, error) { 12 | if id < 1 { 13 | return nil, errors.New("ユーザーIDは1以上である必要があります") 14 | } 15 | 16 | userId := &UserId{ 17 | value: id, 18 | } 19 | return userId, nil 20 | } 21 | 22 | type UserName struct { 23 | value string 24 | } 25 | 26 | func NewUserName(name string) (*UserName, error) { 27 | if len(name) < 3 { 28 | return nil, errors.New("ユーザー名は3文字以上である必要があります") 29 | } 30 | 31 | userName := &UserName{ 32 | value: name, 33 | } 34 | return userName, nil 35 | } 36 | -------------------------------------------------------------------------------- /gin-samples/04test/request_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func setupRouter() *gin.Engine { 13 | r := gin.Default() 14 | 15 | r.GET("/ping", func(c *gin.Context) { 16 | c.String(http.StatusOK, "pong") 17 | }) 18 | 19 | return r 20 | } 21 | 22 | func TestPingRoute(t *testing.T) { 23 | router := setupRouter() 24 | 25 | w := httptest.NewRecorder() 26 | req, _ := http.NewRequest("GET", "/ping", nil) 27 | router.ServeHTTP(w, req) 28 | 29 | assert.Equal(t, 200, w.Code) 30 | assert.Equal(t, "pong", w.Body.String()) 31 | } 32 | -------------------------------------------------------------------------------- /ddd/05application/domain/application/user.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | entity "main/domain/entity" 5 | repository "main/domain/repository" 6 | ) 7 | 8 | type UserApplication struct { 9 | repository repository.UserRepositoryInterface 10 | } 11 | 12 | func NewUserApplication(repository repository.UserRepositoryInterface) *UserApplication { 13 | userApplication := &UserApplication{ 14 | repository: repository, 15 | } 16 | return userApplication 17 | } 18 | 19 | func (ua UserApplication) FindUser(name string) (*entity.User, error) { 20 | user, err := ua.repository.Find(name) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return user, err 27 | } 28 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "main/pkg/setting" 7 | 8 | "gorm.io/driver/mysql" 9 | "gorm.io/gorm" 10 | "gorm.io/gorm/logger" 11 | ) 12 | 13 | var db *gorm.DB 14 | 15 | func Setup() { 16 | dsn := fmt.Sprintf( 17 | "%s:%s@tcp(%s:%s)/%s?parseTime=true", 18 | setting.DatabaseSetting.User, 19 | setting.DatabaseSetting.Password, 20 | setting.DatabaseSetting.Host, 21 | setting.DatabaseSetting.Port, 22 | setting.DatabaseSetting.Name, 23 | ) 24 | var err error 25 | db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 26 | Logger: logger.Default.LogMode(logger.Info), 27 | }) 28 | if err != nil { 29 | log.Fatal(err) 30 | return 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gin-samples/04test/tabledriven_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func Square(a, b int) int { 10 | return a * b 11 | } 12 | 13 | func TestSquere(t *testing.T) { 14 | asserts := assert.New(t) 15 | tests := []struct { 16 | title string 17 | input []int 18 | output int 19 | }{ 20 | { 21 | title: "2x3の面積は6になる", 22 | input: []int{2, 3}, 23 | output: 6, 24 | }, 25 | { 26 | title: "0x1の面積は0になる", 27 | input: []int{0, 1}, 28 | output: 0, 29 | }, 30 | } 31 | 32 | for _, td := range tests { 33 | td := td 34 | t.Run("Square:"+td.title, func(t *testing.T) { 35 | result := Square(td.input[0], td.input[1]) 36 | asserts.Equal(td.output, result) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ddd/01value-object/domain/value-object/money.go: -------------------------------------------------------------------------------- 1 | package valueobject 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type Money struct { 9 | ammount int 10 | currency string 11 | } 12 | 13 | func NewMoney(ammount int, currency string) *Money { 14 | money := &Money{ 15 | ammount: ammount, 16 | currency: currency, 17 | } 18 | return money 19 | } 20 | 21 | func (m Money) Equals(money *Money) bool { 22 | return m.currency == money.currency && m.ammount == money.ammount 23 | } 24 | 25 | func (m Money) Add(money *Money) (*Money, error) { 26 | if m.currency != money.currency { 27 | return nil, errors.New("通貨単位が異なります") 28 | } 29 | 30 | return NewMoney(m.ammount+money.ammount, money.currency), nil 31 | } 32 | 33 | func (m Money) ToString() string { 34 | return fmt.Sprintf("%d(%s)", m.ammount, m.currency) 35 | } 36 | -------------------------------------------------------------------------------- /docs/part0.md: -------------------------------------------------------------------------------- 1 | # 環境構築 2 | 3 | - goenv v2.0.0beta11 4 | - go v1.17.7 5 | 6 | ## goenv 7 | 8 | 参考: https://github.com/syndbg/goenv/blob/master/INSTALL.md 9 | 10 | brewなどでインストールもしくはリポジトリをcloneします。 11 | 12 | ``` 13 | $ brew install goenv 14 | or 15 | $ git clone https://github.com/syndbg/goenv.git ~/.goenv 16 | ``` 17 | 18 | shellに追加して有効にします。 19 | 20 | `~/.bash_profile` などに追加 21 | ``` 22 | export GOENV_ROOT=$HOME/.goenv 23 | export PATH=$GOENV_ROOT/bin:$PATH 24 | eval "$(goenv init -)" 25 | ``` 26 | 27 | インストールリストに目的のバージョンがあるか確認してください。 28 | 29 | ``` 30 | $ goenv --version 31 | goenv 2.0.0beta11 32 | $ goenv install --list 33 | 1.17.7 34 | ``` 35 | 36 | 1.17.7 がなければアップデートしてください。 37 | 38 | ``` 39 | $ cd ~/.goenv && git pull && cd - 40 | ``` 41 | 42 | go v1.17.7をインストールします。 43 | 44 | ``` 45 | $ goenv install 1.17.7 46 | $ goenv global 1.17.7 47 | $ go version 48 | go version go1.17.7 darwin/amd64 49 | ``` 50 | -------------------------------------------------------------------------------- /gin-samples/05orm/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 2 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 3 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 4 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 5 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= 6 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 7 | gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I= 8 | gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= 9 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 10 | gorm.io/gorm v1.23.3 h1:jYh3nm7uLZkrMVfA8WVNjDZryKfr7W+HTlInVgKFJAg= 11 | gorm.io/gorm v1.23.3/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 12 | -------------------------------------------------------------------------------- /gin-samples/03bind/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require github.com/gin-gonic/gin v1.7.7 6 | 7 | require ( 8 | github.com/gin-contrib/sse v0.1.0 // indirect 9 | github.com/go-playground/locales v0.13.0 // indirect 10 | github.com/go-playground/universal-translator v0.17.0 // indirect 11 | github.com/go-playground/validator/v10 v10.4.1 // indirect 12 | github.com/golang/protobuf v1.3.3 // indirect 13 | github.com/json-iterator/go v1.1.9 // indirect 14 | github.com/leodido/go-urn v1.2.0 // indirect 15 | github.com/mattn/go-isatty v0.0.12 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 17 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 18 | github.com/ugorji/go/codec v1.1.7 // indirect 19 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 20 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect 21 | gopkg.in/yaml.v2 v2.2.8 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /gin-samples/01sample/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require github.com/gin-gonic/gin v1.7.7 6 | 7 | require ( 8 | github.com/gin-contrib/sse v0.1.0 // indirect 9 | github.com/go-playground/locales v0.13.0 // indirect 10 | github.com/go-playground/universal-translator v0.17.0 // indirect 11 | github.com/go-playground/validator/v10 v10.4.1 // indirect 12 | github.com/golang/protobuf v1.3.3 // indirect 13 | github.com/json-iterator/go v1.1.9 // indirect 14 | github.com/leodido/go-urn v1.2.0 // indirect 15 | github.com/mattn/go-isatty v0.0.12 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 17 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 18 | github.com/ugorji/go/codec v1.1.7 // indirect 19 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 20 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect 21 | gopkg.in/yaml.v2 v2.2.8 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /gin-samples/02middleware/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require github.com/gin-gonic/gin v1.7.7 6 | 7 | require ( 8 | github.com/gin-contrib/sse v0.1.0 // indirect 9 | github.com/go-playground/locales v0.13.0 // indirect 10 | github.com/go-playground/universal-translator v0.17.0 // indirect 11 | github.com/go-playground/validator/v10 v10.4.1 // indirect 12 | github.com/golang/protobuf v1.3.3 // indirect 13 | github.com/json-iterator/go v1.1.9 // indirect 14 | github.com/leodido/go-urn v1.2.0 // indirect 15 | github.com/mattn/go-isatty v0.0.12 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 17 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 18 | github.com/ugorji/go/codec v1.1.7 // indirect 19 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 20 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect 21 | gopkg.in/yaml.v2 v2.2.8 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /clean-architecture/domain/application/usecase/todo_interactor.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "main/domain/application/repository" 5 | ) 6 | 7 | // Use Case Interactor 8 | type TodoInteractor struct { 9 | OutputPort TodoOutputPortInterface 10 | Repository repository.TodoRepositoryInterface 11 | } 12 | 13 | func NewTodoInteractor( 14 | outputPort TodoOutputPortInterface, 15 | repository repository.TodoRepositoryInterface, 16 | ) TodoInputPortInterface { 17 | return &TodoInteractor{ 18 | OutputPort: outputPort, 19 | Repository: repository, 20 | } 21 | } 22 | 23 | func (i *TodoInteractor) Get(inputData TodoInputData) (*TodoResponse, error) { 24 | 25 | todo, err := i.Repository.Get(inputData.Id) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | outputData := &TodoOutputData{ 31 | Id: todo.Id, 32 | Name: todo.Name, 33 | } 34 | 35 | res, err := i.OutputPort.Convert(*outputData) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return res, nil 41 | } 42 | -------------------------------------------------------------------------------- /clean-architecture/interface/controller/todo.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "main/domain/application/usecase" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | // Controller 10 | type TodoController struct { 11 | InputPort usecase.TodoInputPortInterface 12 | } 13 | 14 | func NewTodoController(inputPort usecase.TodoInputPortInterface) *TodoController { 15 | return &TodoController{ 16 | InputPort: inputPort, 17 | } 18 | } 19 | 20 | func (ctrl *TodoController) GetTodo(context Context) { 21 | 22 | id, err := strconv.Atoi(context.Param("id")) 23 | if err != nil { 24 | context.JSON(http.StatusBadRequest, NewErrorResponse(err.Error())) 25 | context.Abort() 26 | return 27 | } 28 | 29 | inputData := &usecase.TodoInputData{ 30 | Id: id, 31 | } 32 | 33 | res, err := ctrl.InputPort.Get(*inputData) 34 | if err != nil { 35 | context.JSON(http.StatusBadRequest, NewErrorResponse(err.Error())) 36 | context.Abort() 37 | return 38 | } 39 | 40 | context.JSON(http.StatusOK, res) 41 | } 42 | -------------------------------------------------------------------------------- /gin-samples/03bind/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type Request struct { 10 | ID int `uri:"id" json:"id" binding:"required"` 11 | Title string `form:"title" json:"title"` 12 | Score int `form:"score" json:"score"` 13 | } 14 | 15 | func sampleHandler(c *gin.Context) { 16 | request := Request{} 17 | if err := c.ShouldBindUri(&request); err != nil { 18 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 19 | return 20 | } 21 | 22 | if err := c.BindQuery(&request); err != nil { 23 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 24 | return 25 | } 26 | 27 | if err := c.ShouldBind(&request); err != nil { 28 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 29 | return 30 | } 31 | 32 | c.JSON(http.StatusOK, request) 33 | } 34 | 35 | func main() { 36 | r := gin.New() 37 | r.Use(gin.Recovery()) 38 | 39 | r.POST("/sample/:id", sampleHandler) 40 | 41 | r.Run(":8080") 42 | } 43 | -------------------------------------------------------------------------------- /clean-architecture/infrastructure/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | 6 | "main/domain/application/usecase" 7 | "main/infrastructure/router/api" 8 | "main/interface/controller" 9 | "main/interface/presenter" 10 | "main/interface/repository" 11 | ) 12 | 13 | func SetupRouter() *gin.Engine { 14 | todoPresenter := presenter.NewTodoPresenter() 15 | todoRepository := repository.NewTodoRepository() 16 | todoInteractor := usecase.NewTodoInteractor(todoPresenter, todoRepository) 17 | todoController := controller.NewTodoController(todoInteractor) 18 | 19 | router := gin.Default() 20 | apiGroup := router.Group("api") 21 | { 22 | todosGroup := apiGroup.Group("todos") 23 | { 24 | // todosGroup.GET("", api.GetTodos) 25 | todosGroup.GET(":id", api.GetTodoHandler(todoController)) 26 | // todosGroup.POST("", api.CreateTodo) 27 | // todosGroup.PATCH(":id", api.UpdateTodo) 28 | // todosGroup.DELETE(":id", api.DeleteTodo) 29 | } 30 | } 31 | 32 | return router 33 | } 34 | -------------------------------------------------------------------------------- /ddd/05application/domain/repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | entity "main/domain/entity" 5 | vo "main/domain/value-object" 6 | ) 7 | 8 | type UserRepositoryInterface interface { 9 | Exists(user entity.User) bool 10 | Find(name string) (*entity.User, error) 11 | } 12 | 13 | type UserRepository struct{} 14 | 15 | func NewUserRepository() UserRepositoryInterface { 16 | repository := &UserRepository{} 17 | return repository 18 | } 19 | 20 | func (r *UserRepository) Exists(user entity.User) bool { 21 | 22 | // TODO: DBからuser.Idのレコードがあるか確認する処理 23 | exists := false 24 | 25 | return exists 26 | } 27 | 28 | func (r *UserRepository) Find(name string) (*entity.User, error) { 29 | 30 | // TODO: DBからuser.nameを検索してuserオブジェクトを生成して返す 31 | userId, err := vo.NewUserId(1) 32 | if err != nil { 33 | return nil, err 34 | } 35 | userName, err := vo.NewUserName("Moriyuki Arakawa") 36 | if err != nil { 37 | return nil, err 38 | } 39 | user := entity.NewUser(*userId, *userName) 40 | 41 | return user, nil 42 | } 43 | -------------------------------------------------------------------------------- /ddd/01value-object/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | vo "main/domain/value-object" 6 | ) 7 | 8 | func main() { 9 | fullName1 := vo.NewFullName("Moriyuki", "Arakawa") 10 | 11 | // 不変である 12 | // 不変なので内部の値を変更するメソッドを持たない 13 | // fullName1.ChangeFirstName("盛幸") 14 | // fullName1.ChangeLastName("新川") 15 | 16 | // 交換可能である 17 | // なので代入は可能 18 | fullName1 = vo.NewFullName("Moriyuki", "Arakawa") 19 | 20 | // 等価性によって評価される 21 | // なので比較用メソッドを持っている 22 | fullName2 := vo.NewFullName("盛幸", "新川") 23 | fullName1.Equals(fullName2) 24 | log := fmt.Sprintf("Equals: %t", fullName1.Equals(fullName2)) 25 | fmt.Println(log) 26 | 27 | // 値オブジェクトは振る舞いをもつことができる 28 | // Addメソッドは変更ではなく新しい合計Moneyインスタンスを返す 29 | jpMoney1 := vo.NewMoney(100, "JPY") 30 | jpMoney2 := vo.NewMoney(200, "JPY") 31 | totalMoney, _ := jpMoney1.Add(jpMoney2) 32 | fmt.Println(fmt.Sprintf("Total: %s", totalMoney.ToString())) 33 | 34 | // 日本円とドルは加算できないというドメイン知識を表現する 35 | usMoney := vo.NewMoney(300, "USD") 36 | _, err := jpMoney1.Add(usMoney) 37 | if err != nil { 38 | fmt.Println(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /clean-architecture/interface/repository/todo.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "main/domain/application/repository" 7 | "main/domain/entity" 8 | 9 | "gorm.io/driver/mysql" 10 | "gorm.io/gorm" 11 | "gorm.io/gorm/logger" 12 | ) 13 | 14 | type DatabaseInterface interface { 15 | First(dest interface{}, conds ...interface{}) (tx *gorm.DB) 16 | } 17 | 18 | // Data Access 19 | type TodoRepository struct { 20 | db DatabaseInterface 21 | } 22 | 23 | func NewTodoRepository() repository.TodoRepositoryInterface { 24 | 25 | dsn := fmt.Sprintf( 26 | "%s:%s@tcp(%s:%s)/%s?parseTime=true", 27 | "root", 28 | "pass", 29 | "localhost", 30 | "3306", 31 | "ca-sample", 32 | ) 33 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ 34 | Logger: logger.Default.LogMode(logger.Info), 35 | }) 36 | if err != nil { 37 | log.Fatal(err) 38 | return nil 39 | } 40 | 41 | return &TodoRepository{ 42 | db: db, 43 | } 44 | } 45 | 46 | func (r *TodoRepository) Get(id int) (*entity.Todo, error) { 47 | 48 | var todo *entity.Todo 49 | if result := r.db.First(&todo, id); result.Error != nil { 50 | return nil, result.Error 51 | } 52 | 53 | return todo, nil 54 | } 55 | -------------------------------------------------------------------------------- /gin-samples/04test/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.7 7 | github.com/stretchr/testify v1.7.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/gin-contrib/sse v0.1.0 // indirect 13 | github.com/go-playground/locales v0.13.0 // indirect 14 | github.com/go-playground/universal-translator v0.17.0 // indirect 15 | github.com/go-playground/validator/v10 v10.4.1 // indirect 16 | github.com/golang/protobuf v1.3.3 // indirect 17 | github.com/json-iterator/go v1.1.9 // indirect 18 | github.com/leodido/go-urn v1.2.0 // indirect 19 | github.com/mattn/go-isatty v0.0.12 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 21 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/ugorji/go/codec v1.1.7 // indirect 24 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 25 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect 26 | gopkg.in/yaml.v2 v2.2.8 // indirect 27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /gin-samples/01sample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | var db = make(map[string]string) 10 | 11 | func setupRouter() *gin.Engine { 12 | r := gin.Default() 13 | 14 | r.GET("/ping", func(c *gin.Context) { 15 | c.String(http.StatusOK, "pong") 16 | }) 17 | 18 | r.GET("/user/:name", func(c *gin.Context) { 19 | user := c.Params.ByName("name") 20 | value, ok := db[user] 21 | if ok { 22 | c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) 23 | } else { 24 | c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) 25 | } 26 | }) 27 | 28 | authorized := r.Group("/", gin.BasicAuth(gin.Accounts{ 29 | "foo": "bar", // user:foo password:bar 30 | "manu": "123", // user:manu password:123 31 | })) 32 | 33 | authorized.POST("admin", func(c *gin.Context) { 34 | user := c.MustGet(gin.AuthUserKey).(string) 35 | 36 | var json struct { 37 | Value string `json:"value" binding:"required"` 38 | } 39 | 40 | if c.Bind(&json) == nil { 41 | db[user] = json.Value 42 | c.JSON(http.StatusOK, gin.H{"status": "ok"}) 43 | } 44 | }) 45 | 46 | return r 47 | } 48 | 49 | func main() { 50 | r := setupRouter() 51 | r.Run(":8080") 52 | } 53 | -------------------------------------------------------------------------------- /ddd/02entity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | entity "main/domain/entity" 6 | vo "main/domain/value-object" 7 | ) 8 | 9 | func main() { 10 | userId, err := vo.NewUserId(1) 11 | if err != nil { 12 | fmt.Println(err) 13 | } 14 | userName, err := vo.NewUserName("新川 盛幸") 15 | if err != nil { 16 | fmt.Println(err) 17 | } 18 | user := entity.NewUser(*userId, *userName) 19 | 20 | // 可変である 21 | // ので名前を変更することができる 22 | newUserName, err := vo.NewUserName("Moriyuki Arakawa") 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | log := fmt.Sprintf("旧User id:%d name:%s", user.Id, user.Name) 27 | fmt.Println(log) 28 | user.ChangeName(*newUserName) 29 | log = fmt.Sprintf("新User id:%d name:%s", user.Id, user.Name) 30 | fmt.Println(log) 31 | 32 | // 同じ属性であっても区別される 33 | // のでIDが同じではない限り区別される 34 | userId2, err := vo.NewUserId(2) 35 | if err != nil { 36 | fmt.Println(err) 37 | } 38 | userName2, err := vo.NewUserName("Moriyuki Arakawa") 39 | if err != nil { 40 | fmt.Println(err) 41 | } 42 | user2 := entity.NewUser(*userId2, *userName2) 43 | log = fmt.Sprintf("Equals: %t", user.Equals(user2)) 44 | fmt.Println(log) 45 | 46 | // 同一性により区別される 47 | // のでIDが同じなら同一とされる 48 | user3 := entity.NewUser(*userId, *userName2) 49 | log = fmt.Sprintf("Equals: %t", user.Equals(user3)) 50 | fmt.Println(log) 51 | } 52 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/models/todo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type Todo struct { 10 | ID int `gorm:"primarykey" json:"id"` 11 | Name string `json:"name"` 12 | Done bool `json:"done"` 13 | CreatedAt time.Time `json:"created_at"` 14 | UpdatedAt time.Time `json:"updated_at"` 15 | DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"` 16 | } 17 | 18 | func GetTodos() ([]*Todo, error) { 19 | var todos []*Todo 20 | if result := db.Find(&todos); result.Error != nil { 21 | return nil, result.Error 22 | } 23 | 24 | return todos, nil 25 | } 26 | 27 | func GetTodo(id int) (*Todo, error) { 28 | var todo *Todo 29 | if result := db.First(&todo, id); result.Error != nil { 30 | return nil, result.Error 31 | } 32 | 33 | return todo, nil 34 | } 35 | 36 | func CreateTodo(todo *Todo) (*Todo, error) { 37 | if result := db.Create(&todo); result.Error != nil { 38 | return nil, result.Error 39 | } 40 | 41 | return todo, nil 42 | } 43 | 44 | func UpdateTodo(todo *Todo) (*Todo, error) { 45 | if result := db.Model(&todo).Updates(map[string]interface{}{"name": todo.Name, "done": todo.Done}); result.Error != nil { 46 | return nil, result.Error 47 | } 48 | 49 | return todo, nil 50 | } 51 | 52 | func DeleteTodo(id int) error { 53 | // soft delete 54 | var todo *Todo 55 | if result := db.Delete(&todo, id); result.Error != nil { 56 | return result.Error 57 | } 58 | 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /gin-samples/02middleware/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func MyBenchLoggerMiddleware() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | t := time.Now() 14 | 15 | // request 処理の前 16 | // サンプル変数を設定 17 | c.Set("example", "12345") 18 | 19 | c.Next() 20 | 21 | // request 処理の後 22 | // レイテンシ表示 23 | latency := time.Since(t) 24 | log.Print(latency) 25 | 26 | // 送信予定のステータスコードを表示 27 | status := c.Writer.Status() 28 | log.Println(status) 29 | } 30 | } 31 | 32 | func AuthRequiredMiddleware() gin.HandlerFunc { 33 | return func(c *gin.Context) { 34 | // 認証失敗させる 35 | c.JSON(http.StatusUnauthorized, gin.H{"error": true, "from": "AuthRequiredMiddleware"}) 36 | c.Abort() 37 | } 38 | } 39 | 40 | func benchmarkEndpoint(c *gin.Context) { 41 | // MyBenchLoggerMiddlewareで設定された変数を表示 42 | example := c.MustGet("example").(string) 43 | log.Println(example) 44 | 45 | c.JSON(http.StatusOK, gin.H{"error": false, "from": "benchmarkEndpoint"}) 46 | } 47 | 48 | func meEndpoint(c *gin.Context) { 49 | c.JSON(http.StatusOK, gin.H{"error": false, "from": "meEndpoint"}) 50 | } 51 | 52 | func main() { 53 | r := gin.New() 54 | r.Use(gin.Logger()) 55 | r.Use(gin.Recovery()) 56 | 57 | r.GET("/benchmark", MyBenchLoggerMiddleware(), benchmarkEndpoint) 58 | 59 | authorized := r.Group("/auth") 60 | authorized.Use(AuthRequiredMiddleware()) 61 | { 62 | authorized.GET("/me", meEndpoint) 63 | } 64 | 65 | r.Run(":8080") 66 | } 67 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/routers/router_test.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "fmt" 5 | "main/models" 6 | "main/pkg/setting" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | // TODO: テストケース毎のテストデータ挿入や初期化 15 | 16 | func TestRoutersSetupRouter(t *testing.T) { 17 | setting.Setup("../conf/test.ini") 18 | models.Setup() 19 | router := SetupRouter() 20 | 21 | tests := []struct { 22 | Title string 23 | RequestMethod string 24 | RequestPath string 25 | ResponceStatus int 26 | ResponceBody string 27 | }{ 28 | { 29 | Title: "データがない場合空配列を返す", 30 | RequestMethod: "GET", 31 | RequestPath: "/api/todos", 32 | ResponceStatus: 200, 33 | ResponceBody: "[]", 34 | }, 35 | { 36 | Title: "指定IDのデータがない場合status:400でエラーメッセージを返す", 37 | RequestMethod: "GET", 38 | RequestPath: "/api/todos/1", 39 | ResponceStatus: 400, 40 | ResponceBody: "{\"message\":\"record not found\"}", 41 | }, 42 | } 43 | 44 | for _, td := range tests { 45 | td := td 46 | title := fmt.Sprintf( 47 | "SetupRouter %s %s %s", 48 | td.RequestMethod, 49 | td.RequestPath, 50 | td.Title, 51 | ) 52 | t.Run(title, func(t *testing.T) { 53 | w := httptest.NewRecorder() 54 | req, _ := http.NewRequest(td.RequestMethod, td.RequestPath, nil) 55 | router.ServeHTTP(w, req) 56 | 57 | assert.Equal(t, w.Code, td.ResponceStatus) 58 | assert.Equal(t, w.Body.String(), td.ResponceBody) 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /clean-architecture/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.7 7 | github.com/go-ini/ini v1.66.4 8 | github.com/stretchr/testify v1.7.1 9 | gorm.io/driver/mysql v1.3.2 10 | gorm.io/gorm v1.23.2 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/gin-contrib/sse v0.1.0 // indirect 16 | github.com/go-playground/locales v0.14.0 // indirect 17 | github.com/go-playground/universal-translator v0.18.0 // indirect 18 | github.com/go-playground/validator/v10 v10.10.1 // indirect 19 | github.com/go-sql-driver/mysql v1.6.0 // indirect 20 | github.com/golang/protobuf v1.5.2 // indirect 21 | github.com/jinzhu/inflection v1.0.0 // indirect 22 | github.com/jinzhu/now v1.1.5 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/leodido/go-urn v1.2.1 // indirect 25 | github.com/mattn/go-isatty v0.0.14 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/ugorji/go/codec v1.2.7 // indirect 30 | golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect 31 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect 32 | golang.org/x/text v0.3.7 // indirect 33 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 34 | google.golang.org/protobuf v1.27.1 // indirect 35 | gopkg.in/yaml.v2 v2.4.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.7 7 | github.com/go-ini/ini v1.66.4 8 | github.com/stretchr/testify v1.7.1 9 | gorm.io/driver/mysql v1.3.2 10 | gorm.io/gorm v1.23.2 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/gin-contrib/sse v0.1.0 // indirect 16 | github.com/go-playground/locales v0.14.0 // indirect 17 | github.com/go-playground/universal-translator v0.18.0 // indirect 18 | github.com/go-playground/validator/v10 v10.10.1 // indirect 19 | github.com/go-sql-driver/mysql v1.6.0 // indirect 20 | github.com/golang/protobuf v1.5.2 // indirect 21 | github.com/jinzhu/inflection v1.0.0 // indirect 22 | github.com/jinzhu/now v1.1.5 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/leodido/go-urn v1.2.1 // indirect 25 | github.com/mattn/go-isatty v0.0.14 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/pmezard/go-difflib v1.0.0 // indirect 29 | github.com/ugorji/go/codec v1.2.7 // indirect 30 | golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect 31 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect 32 | golang.org/x/text v0.3.7 // indirect 33 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 34 | google.golang.org/protobuf v1.27.1 // indirect 35 | gopkg.in/yaml.v2 v2.4.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /gin-samples/05orm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type Todo struct { 12 | gorm.Model 13 | Name string `json:"name"` 14 | Done bool `json:"done"` 15 | } 16 | 17 | func main() { 18 | dsn := "root:pass@tcp(localhost:3306)/gin-sample" 19 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 20 | if err != nil { 21 | log.Fatal(err) 22 | return 23 | } 24 | 25 | // Create 26 | if result := db.Create(&Todo{Name: "test", Done: false}); result.Error != nil { 27 | log.Fatal(result.Error) 28 | return 29 | } 30 | 31 | // Read 32 | var todo Todo 33 | if result := db.First(&todo, 1); result.Error != nil { 34 | log.Fatal(result.Error) 35 | return 36 | } 37 | fmt.Println(todo.Name) 38 | 39 | if result := db.First(&todo, "name = ?", "test"); result.Error != nil { 40 | log.Fatal(result.Error) 41 | return 42 | } 43 | fmt.Println(todo.Name) 44 | 45 | // Update 46 | if result := db.Model(&todo).Update("name", "changed"); result.Error != nil { 47 | log.Fatal(result.Error) 48 | return 49 | } 50 | fmt.Println(todo.Name) 51 | 52 | if result := db.Model(&todo).Updates(&Todo{Name: "changed 2", Done: true}); result.Error != nil { 53 | log.Fatal(result.Error) 54 | return 55 | } 56 | fmt.Println(todo.Name) 57 | fmt.Println(todo.Done) 58 | 59 | if result := db.Model(&todo).Updates(map[string]interface{}{"name": "changed 3", "done": false}); result.Error != nil { 60 | log.Fatal(result.Error) 61 | return 62 | } 63 | fmt.Println(todo.Name) 64 | fmt.Println(todo.Done) 65 | 66 | // Delete (soft delete) 67 | if result := db.Delete(&todo, 1); result.Error != nil { 68 | log.Fatal(result.Error) 69 | return 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /clean-architecture/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## tree 3 | 4 | ``` 5 | ├── domain ドメイン層 6 | │   ├── application Application Business Rules 7 | │   │   ├── repository リポジトリのインターフェース 8 | │   │   │   └── todo.go 9 | │   │   └── usecase Usecase 10 | │   │   ├── todo_input_port.go Input PortのインターフェースとInput Dataの構造定義。ドメインが望む入力形式の定義 11 | │   │   ├── todo_interactor.go Interactor。Input DataとRepositoryを使ってデータを取得、Output Portの実装であるPresenterに渡す 12 | │   │   └── todo_output_port.go Output PortのインターフェースとOutput Dataの構造定義。。ドメインが望む出力形式の定義 13 | │   └── entity Enterprise Business Rules 14 | │   └── todo.go データ構造。DDDにおける値オブジェクトやドメインサービスまで定義していいと思う。 15 | ├── go.mod 16 | ├── go.sum 17 | ├── infrastructure Frameworks & Drivers 18 | │   └── router Ginの実装。Controllerと繋げる 19 | │   ├── api 20 | │   │   └── todo.go 21 | │   └── router.go 22 | ├── interface Interface Adapters 23 | │   ├── controller Controller。データをInteractorが望むInputDataに整形して渡し、Presenterが整形したデータを表示する 24 | │   │   ├── context.go 25 | │   │   ├── error.go 26 | │   │   └── todo.go 27 | │   ├── presenter Presenter。Output Portの実装。OutDataを受け取ってレスポンスを整形する 28 | │   │ └── todo.go 29 | │   └── repository 30 | │      └── todo.go リポジトリの実装。Databaseを使ったデータの取得など 31 | ├── main.go 32 | ├── migrations 33 | │   ├── 000001_create_todos.down.sql 34 | │   └── 000001_create_todos.up.sql 35 | └── readme.md 36 | ``` 37 | 38 | ## DB setup 39 | 40 | ``` 41 | $ cd /path/to/go-clean-handson 42 | $ docker compose up 43 | 44 | $ mysql --host=127.0.0.1 --port=3306 --user=root --password=pass 45 | mysql> create database `ca-sample` default character set utf8mb4 collate utf8mb4_bin; 46 | 47 | $ cd /path/to/go-clean-handson/clean-architecture 48 | $ export DATABASE_URL='mysql://root:pass@tcp(localhost:3306)/ca-sample' 49 | $ migrate -database ${DATABASE_URL} -path migrations up 50 | ``` -------------------------------------------------------------------------------- /gin-samples/06todoapp/routers/api/todo.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "main/models" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type GetTodoRequest struct { 11 | ID int `uri:"id" json:"id" binding:"required"` 12 | } 13 | 14 | type CreateTodoRequest struct { 15 | Name string `form:"name" binding:"required"` 16 | Done *bool `form:"done" binding:"required"` 17 | } 18 | 19 | type UpdateTodoRequest struct { 20 | ID int `uri:"id" binding:"required"` 21 | Name *string `form:"name"` 22 | Done *bool `form:"done"` 23 | } 24 | 25 | type DeleteTodoRequest struct { 26 | ID int `uri:"id" json:"id" binding:"required"` 27 | } 28 | 29 | func GetTodos(c *gin.Context) { 30 | todos, err := models.GetTodos() 31 | if err != nil { 32 | c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) 33 | c.Abort() 34 | return 35 | } 36 | 37 | c.JSON(http.StatusOK, todos) 38 | } 39 | 40 | func GetTodo(c *gin.Context) { 41 | req := GetTodoRequest{} 42 | if err := c.ShouldBindUri(&req); err != nil { 43 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 44 | return 45 | } 46 | 47 | todo, err := models.GetTodo(req.ID) 48 | if err != nil { 49 | c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) 50 | c.Abort() 51 | return 52 | } 53 | 54 | c.JSON(http.StatusOK, todo) 55 | } 56 | 57 | func CreateTodo(c *gin.Context) { 58 | req := CreateTodoRequest{} 59 | if err := c.ShouldBind(&req); err != nil { 60 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 61 | return 62 | } 63 | 64 | todo, err := models.CreateTodo(&models.Todo{ 65 | Name: req.Name, 66 | Done: *req.Done, 67 | }) 68 | if err != nil { 69 | c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) 70 | c.Abort() 71 | return 72 | } 73 | 74 | c.JSON(http.StatusCreated, todo) 75 | } 76 | 77 | func UpdateTodo(c *gin.Context) { 78 | req := UpdateTodoRequest{} 79 | if err := c.ShouldBindUri(&req); err != nil { 80 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 81 | return 82 | } 83 | if err := c.ShouldBind(&req); err != nil { 84 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 85 | return 86 | } 87 | 88 | todo, err := models.UpdateTodo(&models.Todo{ 89 | ID: req.ID, 90 | Name: *req.Name, 91 | Done: *req.Done, 92 | }) 93 | if err != nil { 94 | c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) 95 | c.Abort() 96 | return 97 | } 98 | 99 | c.JSON(http.StatusOK, todo) 100 | } 101 | 102 | func DeleteTodo(c *gin.Context) { 103 | req := DeleteTodoRequest{} 104 | if err := c.ShouldBindUri(&req); err != nil { 105 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 106 | return 107 | } 108 | 109 | err := models.DeleteTodo(req.ID) 110 | if err != nil { 111 | c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()}) 112 | c.Abort() 113 | return 114 | } 115 | 116 | c.Status(http.StatusNoContent) 117 | } 118 | -------------------------------------------------------------------------------- /gin-samples/03bind/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 6 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 7 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 8 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 17 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 18 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 20 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 21 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 22 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 23 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 24 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 25 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 26 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 27 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 28 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 33 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 34 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 35 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 36 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 37 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 38 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 46 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 54 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 55 | -------------------------------------------------------------------------------- /gin-samples/01sample/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 6 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 7 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 8 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 17 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 18 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 20 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 21 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 22 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 23 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 24 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 25 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 26 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 27 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 28 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 33 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 34 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 35 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 36 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 37 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 38 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 46 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 54 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 55 | -------------------------------------------------------------------------------- /gin-samples/02middleware/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 6 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 7 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 8 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 17 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 18 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 20 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 21 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 22 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 23 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 24 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 25 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 26 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 27 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 28 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 33 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 34 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 35 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 36 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 37 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 38 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 40 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 46 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 48 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 54 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 55 | -------------------------------------------------------------------------------- /gin-samples/04test/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 6 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 7 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 8 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 17 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 18 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 20 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 21 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 22 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 23 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 24 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 25 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 26 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 27 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 28 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 33 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 34 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 35 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 36 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 37 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 38 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 39 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 40 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 42 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 44 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 45 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 47 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 49 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 50 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 54 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 55 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 56 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 57 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 58 | -------------------------------------------------------------------------------- /clean-architecture/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 6 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 7 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 8 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 9 | github.com/go-ini/ini v1.66.4 h1:dKjMqkcbkzfddhIhyglTPgMoJnkvmG+bSLrU9cTHc5M= 10 | github.com/go-ini/ini v1.66.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 11 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 12 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 13 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 14 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 15 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 16 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 17 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 18 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 19 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 20 | github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= 21 | github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 22 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 23 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 24 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 25 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 26 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 27 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 32 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 33 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 34 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 35 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 36 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 37 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 38 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 39 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 40 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 41 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 42 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 43 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 44 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 45 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 46 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 47 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 48 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 49 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 50 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 51 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 52 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 53 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 56 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 57 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 58 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 59 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 63 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 64 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 65 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 67 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 68 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 69 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 70 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 71 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 72 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 73 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 74 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 75 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 76 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 77 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 78 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 79 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 80 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 81 | golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= 82 | golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 83 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 84 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 85 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 86 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 89 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 90 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= 94 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 96 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 97 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 98 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 99 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 100 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 101 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 102 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 104 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 106 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 107 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 108 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 109 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 110 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 111 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 112 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 113 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 114 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 115 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 116 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 117 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 118 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 119 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 120 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 121 | gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I= 122 | gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= 123 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 124 | gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ= 125 | gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 126 | -------------------------------------------------------------------------------- /gin-samples/06todoapp/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 6 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 7 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 8 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 9 | github.com/go-ini/ini v1.66.4 h1:dKjMqkcbkzfddhIhyglTPgMoJnkvmG+bSLrU9cTHc5M= 10 | github.com/go-ini/ini v1.66.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 11 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 12 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 13 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 14 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 15 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 16 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 17 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 18 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 19 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 20 | github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= 21 | github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 22 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 23 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 24 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 25 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 26 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 27 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 32 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 33 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 34 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 35 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 36 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 37 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 38 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 39 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 40 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 41 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 42 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 43 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 44 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 45 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 46 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 47 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 48 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 49 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 50 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 51 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 52 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 53 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 56 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 57 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 58 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 59 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 63 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 64 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 65 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 67 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 68 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 69 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 70 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 71 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 72 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 73 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 74 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 75 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 76 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 77 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 78 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 79 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 80 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 81 | golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= 82 | golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 83 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 84 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 85 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 86 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 89 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 90 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= 94 | golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 96 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 97 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 98 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 99 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 100 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 101 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 102 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 104 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 106 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 107 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 108 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 109 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 110 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 111 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 112 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 113 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 114 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 115 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 116 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 117 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 118 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 119 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 120 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 121 | gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I= 122 | gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= 123 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 124 | gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ= 125 | gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 126 | -------------------------------------------------------------------------------- /docs/part3.md: -------------------------------------------------------------------------------- 1 | # DDD 入門 2 | 3 | ## 概要 4 | 5 | - [エリック・エヴァンスのドメイン駆動設計](https://www.amazon.co.jp/dp/4798121967/) 6 | 7 | ### DDDとは 8 | 9 | `ソフトウェアの核心にある複雑さに立ち向かう` 10 | 11 | > まずは、DDDとは何か、から始めましょう。そもそも「ドメイン」とは、アプリケーションが対象とする業務領域のことです。本書では、ドメインを「知識、影響力、活動の一領域」と定義しています。 12 | > DDDは、オブジェクト指向コミュニティの間で長年培われてきたドメインモデリングのノウハウやベストプラクティスを集大成した、1つの設計思想/哲学です。ドメインモデルをこれから構築しようとする人に、設計上の判断を下すための枠組みと、ドメイン設計について議論するためのボキャブラリを提供するものです。 13 | 14 | *[Domain-Driven Designのエッセンス](https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap1.html)より引用 15 | 16 | ### DDDの目的 17 | 18 | `ソフトウェアの価値を高めること` 19 | 20 | `ソフトウェアの機能性と保守性の両方を高めること` 21 | 22 | 噛み砕くと 23 | 24 | 1. 価値のあるソフトウェアを開発したい 25 | 2. (誤解を恐れず言えば)価値のあるソフトウェアの要件は大抵複雑 26 | 3. ソフトウェアはジェンガで機能を積めば積むほど崩壊しやすい 27 | - 複雑で解析に時間がかかる 28 | - 影響範囲の特定に時間がかかる 29 | - 慎重に開発が必要になる 30 | 4. DDD(やクリーンアーキテクチャ)は土台から固めて機能を積む 31 | - 初期コストはかかる(学習コストやコード量が多い) 32 | - 長期的なメンテナンス性や安定したリリース周期に貢献する 33 | 34 | DDDの導入は `コードの品質を上げること` も一つの目的かもしれないが、 35 | 36 | ドメインエキスパート(開発しているソフトウエアの適用領域に深く通じている人)とのコミュニケーション(ヒアリングや要件定義や定例MTGなど)を通して対象ドメインへのより深い理解を得て、顧客ですら見えていない問題解決のための機能性を持った `価値のあるソフトウェア` を作ることが目的です。 37 | 38 | [顧客が本当に必要だったもの](https://dic.nicovideo.jp/a/%E9%A1%A7%E5%AE%A2%E3%81%8C%E6%9C%AC%E5%BD%93%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%A0%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE)にも通ずる話 39 | 40 | また、DDDのプラクティスを実践することで長期的なメンテナンス性や安定したリリース周期に貢献し、ひいてはそれも含めた `価値のあるソフトウェア` を作ることも目的です。 41 | 42 | ### 根本的なDDDの3原則 43 | 44 | > - コアドメインに集中すること 45 | > - ドメインの実践者とソフトウェアの実践者による創造的な共同作業を通じて、モデルを探求すること 46 | > - 明示的な境界づけられたコンテキストの内部で、ユビキタス言語を語ること 47 | 48 | *[ドメイン駆動設計入門 成瀬允宣(著)](https://www.amazon.co.jp/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E5%85%A5%E9%96%80-%E3%83%9C%E3%83%88%E3%83%A0%E3%82%A2%E3%83%83%E3%83%97%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E3%81%AE%E5%9F%BA%E6%9C%AC-%E6%88%90%E7%80%AC-%E5%85%81%E5%AE%A3/dp/479815072X)より引用 49 | 50 | ### 今回のハンズオンで学ぶ範囲 51 | 52 | DDDは設計思想/哲学とあるように広く実践的なプラクティスの集大成で、プログラマーだけで実践/完結するものではありません。 53 | 54 | が、今回のハンズオンでは主にプログラマーだけで実践できる `コアドメインに集中すること` について深掘ります。 55 | 56 | 俗に `戦術的DDD` または `軽量DDD` と呼ばれています。 57 | 58 | ドメインが何か、なぜ重要なのか知ることで様々なアーキテクチャへの理解につながります。 59 | 60 | 様々なアーキテクチャへの理解することで、様々なプロジェクトで必要なシーンに遭遇した時、設計する上での選択肢を増やすことができます。 61 | 62 | ## ドメインとは 63 | 64 | > 「ドメイン」とは、アプリケーションが対象とする業務領域のことです。 65 | 66 | プログラミングにおけるドメインでいうと 67 | 68 | `4層アーキテクチャ` の `ドメイン層` 69 | 70 | - プレゼンテーション層 (UIを含むリクエストやレスポンスで外部をやりとりする層) 71 | - アプリケーション層 (全体をコントロールするプレゼンテーション層とドメイン層の仲介役) 72 | - ドメイン層 (オブジェクトや型、ロジックを含むモデル化された業務仕様、ビジネスロジック) 73 | - インフラストラクチャ層 (DBや外部システムとの連携など環境に依存する実装) 74 | 75 | `MVC(or MVC2)アーキテクチャ` の `Model` 76 | 77 | - Controller (リクエストを受け付け、Modelを呼び出して処理し、Viewに伝えてレスポンスを返す) 78 | - Model (オブジェクトや型、ロジックを含むモデル化された業務仕様、ビジネスロジック) 79 | - View (モデルを元にHTMLへ整形してUI、またはJSONを整形してレスポンスを出力) 80 | 81 | *DAO(Data Access Object)やOR/Mapperを含んだモデルのことではないことには注意 82 | 83 | ### 具体例 84 | 85 | ドメインはシステム化する上で必ず必要になるロジックやオブジェクトと置き換えて考えてもらって構いません。 86 | 87 | TODOリストアプリケーションの場合、以下のようなロジックやオブジェクトがドメインとなります。 88 | 89 | - Modelが持つaddTodo 90 | - addTodoが受ける引数の型 91 | - addTodoが返す戻り値の型 92 | - バリデーションロジック(同じ名前のタスクは登録できない等) 93 | 94 | DBへ接続するTODOリストWebアプリケーションの例で考えてみましょう。 95 | 96 | ![](./part3_todo_web_domain.svg) 97 | 98 | CSVへ保存するCLIツールとしてTODOリストを実装する場合はこうなります。 99 | 100 | ![](./part3_todo_cli_domain.svg) 101 | 102 | 解決手段は変わったものの、ドメインは変わっていません。 103 | 104 | フレームワークやデータベースはシステム化する上で必ず必要になるロジックやオブジェクトではない外側の知識であることがわかります。 105 | 106 | また、スマホアプリや組み込みシステムなど、ソリューションが違っても変わらない知識がドメインです。 107 | 108 | 109 | ## ドメインの構成要素 110 | 111 | DDDにおけるドメインは以下の要素に分けられて定義されます。 112 | 113 | - 知識を表現するパターン 114 | - 値オブジェクト 115 | - エンティティ 116 | - ドメインサービス 117 | - アプリケーションを実現するためのパターン 118 | - リポジトリ 119 | - アプリケーションサービス 120 | - ファクトリ 121 | - 知識を表現する、より発展的なパターン 122 | - 集約 123 | - 仕様 124 | 125 | それぞれを深ぼる前にざっくり構成要素と関係についてみてみましょう。 126 | 127 | ![](./part3_domain.svg) 128 | 129 | 130 | ## 値オブジェクト 131 | 132 | システム固有の値を表現するものが `値オブジェクト` で、以下の特徴があります。 133 | 134 | - 不変である 135 | - 交換可能である 136 | - 等価性によって評価される 137 | 138 | コードで例をみてみましょう。 139 | 140 | 以下はフルネームを表現する値オブジェクトの例です。 141 | 142 | `./ddd/01value-object/domain/value-object/fullname.go` 143 | 144 | ```go 145 | package valueobject 146 | 147 | type FullName struct { 148 | firstName string 149 | lastName string 150 | } 151 | 152 | func NewFullName(firstName string, lastName string) *FullName { 153 | fullName := &FullName{ 154 | firstName: firstName, 155 | lastName: lastName, 156 | } 157 | return fullName 158 | } 159 | 160 | func (f FullName) Equals(fullName *FullName) bool { 161 | return f.firstName == fullName.firstName && f.lastName == fullName.lastName 162 | } 163 | ``` 164 | 165 | `./ddd/01value-object/main.go` 166 | 167 | ```go 168 | fullName1 := vo.NewFullName("Moriyuki", "Arakawa") 169 | 170 | // 不変である 171 | // 不変なので以下のような内部の値を変更するメソッドや直接書き換え可能な公開プロパティを持たない 172 | fullName1.ChangeFirstName("盛幸") 173 | fullName1.ChangeLastName("新川") 174 | fullName1.FirstName = "盛幸" 175 | fullName1.LastName = "新川" 176 | 177 | // 交換可能である 178 | // なので代入は可能 179 | fullName1 = vo.NewFullName("Moriyuki", "Arakawa") 180 | 181 | // 等価性によって評価される 182 | // なので比較用メソッドを持っている 183 | fullName2 := vo.NewFullName("盛幸", "新川") 184 | fullName1.Equals(fullName2) 185 | log := fmt.Sprintf("Equals: %t", fullName1.Equals(fullName2)) 186 | fmt.Println(log) // Equals: false 187 | ``` 188 | 189 | 値オブジェクトは不変なので、`ChangeFirstName` や `ChangeLastName` のような内部の値を破壊的に変更するようなメソッドを持ってはいけません。 190 | 191 | プリミティブ型(基本データ型)の `string` や `int` のように不変です。 192 | 193 | 交換可能なので代入は可能です。 194 | 195 | 等価性によって評価されるので、比較用メソッドを持っています。 196 | 197 | --- 198 | 199 | 値オブジェクトを定義していない場合、同じ名前か調べる処理があれば至る所に同じfirstNameとlastNameの比較処理が書かれることになります。 200 | 201 | 後にミドルネーム対応など仕様変更があった場合、FullNameというドメイン知識を値オブジェクトが表現してくれているので属性が追加されても修正が最小限になります。 202 | 203 | --- 204 | 205 | 値オブジェクトは振る舞いをもつことができます。 206 | 207 | 以下はお金を表現し、足し算ができる値オブジェクトの例です。 208 | 209 | `./ddd/01value-object/domain/value-object/money.go` 210 | 211 | ```go 212 | package valueobject 213 | 214 | import ( 215 | "errors" 216 | "fmt" 217 | ) 218 | 219 | type Money struct { 220 | ammount int 221 | currency string 222 | } 223 | 224 | func NewMoney(ammount int, currency string) *Money { 225 | money := &Money{ 226 | ammount: ammount, 227 | currency: currency, 228 | } 229 | return money 230 | } 231 | 232 | func (m Money) Equals(money *Money) bool { 233 | return m.currency == money.currency && m.ammount == money.ammount 234 | } 235 | 236 | func (m Money) Add(money *Money) (*Money, error) { 237 | if m.currency != money.currency { 238 | return nil, errors.New("通貨単位が異なります") 239 | } 240 | 241 | return NewMoney(m.ammount+money.ammount, money.currency), nil 242 | } 243 | 244 | func (m Money) ToString() string { 245 | return fmt.Sprintf("%d(%s)", m.ammount, m.currency) 246 | } 247 | ``` 248 | 249 | `./ddd/01value-object/main.go` 250 | 251 | ```go 252 | // 値オブジェクトは振る舞いをもつことができる 253 | // Addメソッドは変更ではなく新しい合計Moneyインスタンスを返す 254 | jpMoney1 := vo.NewMoney(100, "JPY") 255 | jpMoney2 := vo.NewMoney(200, "JPY") 256 | totalMoney, _ := jpMoney1.Add(jpMoney2) 257 | fmt.Println(fmt.Sprintf("Total: %s", totalMoney.ToString())) // Total: 300(JPY) 258 | 259 | // 日本円とドルは加算できないというドメイン知識を表現する 260 | usMoney := vo.NewMoney(300, "USD") 261 | _, err := jpMoney1.Add(usMoney) 262 | if err != nil { 263 | fmt.Println(err) // 通貨単位が異なります 264 | } 265 | ``` 266 | 267 | 自身を変更しない限り、メソッドは自由に作ることができます。 268 | 269 | 値オブジェクトの足し算などは自身への変更ではなく新しい合計Moneyインスタンスを返すようにします。 270 | 271 | また、日本円とドルは加算できないというドメイン知識を表現することもできます。 272 | 273 | --- 274 | 275 | 他にも値オブジェクトは以下のようなプラクティスで実装することができます。 276 | 277 | - 不正な値を存在させない 278 | - フルネームは姓と名はそれぞれ1文字以上、お金は0以上などのルールをコンストラクタで検証してエラーを返すなど 279 | - 謝った代入を防ぐ 280 | - IDとint型を明確に区別するため、UserIdなどの値オブジェクトを用意する 281 | - ロジックの散在を防ぐ 282 | - バリデーションを値オブジェクトに持たせることで、作成や更新時の検証処理を散在させない 283 | 284 | 285 | ## エンティティ 286 | 287 | ライフサイクルのあるオブジェクトが `エンティティ` で、以下の特徴があります。 288 | 289 | - 可変である 290 | - 同じ属性であっても区別される 291 | - 同一性により区別される 292 | 293 | コードで例をみてみましょう。 294 | 295 | まずは `エンティティ` であるユーザーオブジェクトが使う `値オブジェクト` から見てみます。 296 | 297 | `./ddd/02entity/domain/value-object/user.go` 298 | 299 | ```go 300 | package valueobject 301 | 302 | import ( 303 | "errors" 304 | ) 305 | 306 | type UserId struct { 307 | value int 308 | } 309 | 310 | func NewUserId(id int) (*UserId, error) { 311 | if id < 1 { 312 | return nil, errors.New("ユーザーIDは1以上である必要があります") 313 | } 314 | 315 | userId := &UserId{ 316 | value: id, 317 | } 318 | return userId, nil 319 | } 320 | 321 | type UserName struct { 322 | value string 323 | } 324 | 325 | func NewUserName(name string) (*UserName, error) { 326 | if len(name) < 3 { 327 | return nil, errors.New("ユーザー名は3文字以上である必要があります") 328 | } 329 | 330 | userName := &UserName{ 331 | value: name, 332 | } 333 | return userName, nil 334 | } 335 | ``` 336 | 337 | `User` エンティティが利用する `UserId` や `UserName` の値オブジェクト定義しています。 338 | 339 | それぞれの値オブジェクトは `何文字以上` などのドメイン知識がコードに反映されています。 340 | 341 | ドメイン知識を反映せず、単純なsetter/getterでも実装可能ですが、DDDではコードのドキュメント性を高めるため、正しい仕様を正しいオブジェクトに記載します。 342 | 343 | これにより、ドメインモデル貧血症やロジックの分散を防ぐことができます。 344 | 345 | 次は本題であるユーザーを表現するエンティティの例です。 346 | 347 | `./ddd/02entity/domain/entity/user.go` 348 | 349 | ```go 350 | package entiry 351 | 352 | import ( 353 | vo "main/domain/value-object" 354 | ) 355 | 356 | type User struct { 357 | Id vo.UserId 358 | Name vo.UserName 359 | } 360 | 361 | func NewUser(id vo.UserId, name vo.UserName) *User { 362 | user := &User{ 363 | Id: id, 364 | Name: name, 365 | } 366 | return user 367 | } 368 | 369 | func (u User) Equals(user *User) bool { 370 | return u.Id == user.Id 371 | } 372 | 373 | func (u *User) ChangeName(name vo.UserName) { 374 | u.Name = name 375 | } 376 | ``` 377 | 378 | `./ddd/02entity/main.go` 379 | 380 | ```go 381 | userId, _ := vo.NewUserId(1) 382 | userName, _ := vo.NewUserName("新川 盛幸") 383 | user := entity.NewUser(*userId, *userName) 384 | 385 | // 可変である 386 | // ので名前を変更することができる 387 | newUserName, _ := vo.NewUserName("Moriyuki Arakawa") 388 | log := fmt.Sprintf("旧User id:%d name:%s", user.Id, user.Name) 389 | fmt.Println(log) // 旧User id:{1} name:{新川 盛幸} 390 | user.ChangeName(*newUserName) 391 | log = fmt.Sprintf("新User id:%d name:%s", user.Id, user.Name) 392 | fmt.Println(log) // 新User id:{1} name:{Moriyuki Arakawa} 393 | 394 | // 同じ属性であっても区別される 395 | // のでIDが同じではない限り区別される 396 | userId2, _ := vo.NewUserId(2) 397 | userName2, _ := vo.NewUserName("Moriyuki Arakawa") 398 | user2 := entity.NewUser(*userId2, *userName2) 399 | log = fmt.Sprintf("Equals: %t", user.Equals(user2)) 400 | fmt.Println(log) // Equals: false 401 | 402 | // 同一性により区別される 403 | // のでIDが同じなら同一とされる 404 | user3 := entity.NewUser(*userId, *userName2) 405 | log = fmt.Sprintf("Equals: %t", user.Equals(user3)) 406 | fmt.Println(log) // Equals: true 407 | ``` 408 | 409 | ユーザーは名前や年齢、身長など変更される可能性のあるエンティティです。 410 | 411 | 可変であるので名前を変更することができます。 412 | 413 | ただ、同姓同名のユーザーもいるので、同じ名前(属性)であっても区別されます。 414 | 415 | 同一性により区別されるので `ID` (やメモリアドレス)により区別されます。 416 | 417 | *基本的に可変なオブジェクトは厄介で、全ての値を可変にする必要はありません。必要に応じて属性を可変にすることが許されてるに過ぎません。 418 | 419 | 420 | ## ドメインサービス 421 | 422 | 不自然さを解決するオブジェクトが `ドメインサービス` です。 423 | 424 | 不自然さとは何かを説明します。 425 | 426 | 例として、システムによってユーザー名の重複を許さないパターンもあるでしょう。 427 | 428 | ユーザー名の重複を許さないというのはドメインのルールで、ドメインに書く必要があります。 429 | 430 | 先ほどのユーザーオブジェクトに重複確認の関数をはやしてみます。 431 | 432 | ```go 433 | user := entity.NewUser(*userId, *userName) 434 | 435 | if user.Exists(user) { 436 | // 重複している 437 | } else { 438 | // 重複していない 439 | } 440 | ``` 441 | 442 | これはメソッドとして定義した場合の `user.Exists()` も同様ですが、ユーザーオブジェクト自身に問い合わせています。 443 | 444 | この場合、重複チェックが自身を含めているのか、自身を除外しているのか、不明瞭です。 445 | 446 | こういった処理をユーザーオブジェクトに定義すると開発者を惑わせます。 447 | 448 | 以下のようなドメインサービスを定義して不自然さを解決してみましょう。 449 | 450 | `./ddd/03domain-service/domain/domain-service/user.go` 451 | 452 | ```go 453 | package domainservice 454 | 455 | import ( 456 | entity "main/domain/entity" 457 | ) 458 | 459 | func UserExists(user entity.User) bool { 460 | 461 | // TODO: 重複確認処理 462 | exists := false 463 | 464 | return exists 465 | } 466 | ``` 467 | 468 | `./ddd/03domain-service/main.go` 469 | 470 | ```go 471 | user := entity.NewUser(*userId, *userName) 472 | 473 | ds.UserExists(*user) 474 | ``` 475 | 476 | *具体的な処理内容は本質ではないため省略しています。 477 | 478 | ドメインサービスは状態を持たないオブジェクト(または関数など)です。 479 | 480 | 値オブジェクトやエンティティに定義すると不自然になる関数はドメインサービスに定義しましょう。 481 | 482 | --- 483 | 484 | 複数の値オブジェクトやエンティティが絡む処理などはドメインサービスに定義する方がいいでしょう。 485 | 486 | ドメインサービスは便利でなんでも定義できてしまうサービスです。濫用はかえってドメインモデルがドメイン意識を語らない `ドメインモデル貧血症` を起こすことにつながります。 487 | 488 | 例えばUserServiceにChangeNameを定義するなどドメインサービスに過剰な責任を押し付ける一つの例です。 489 | 490 | --- 491 | 492 | ドメイン全般に言えますが、UserService.Existsにはデータベースの操作などドメインの外側の知識を書いてはいけません。 493 | 494 | DBの操作をせずに重複確認ができるのか、実際問題どうするかは次のリポジトリで解決します。 495 | 496 | 497 | ## リポジトリ 498 | 499 | データにまつわる処理を分離するオブジェクトが `リポジトリ` で、永続化処理を担当しています。 500 | 501 | 先ほどのセクションで、リポジトリ以外でデータベースの操作はしてはいけない話をしましたが、全てリポジトリを通して操作されます。 502 | 503 | リポジトリを経由して操作するだけですが、ソフトウェアに柔軟性を与えます。 504 | 505 | リポジトリの実装を見てみましょう。 506 | 507 | `./ddd/04repository/domain/repository/user.go` 508 | 509 | ```go 510 | package repository 511 | 512 | import ( 513 | entity "main/domain/entity" 514 | ) 515 | 516 | type UserRepositoryInterface interface { 517 | Exists(user entity.User) bool 518 | } 519 | 520 | type UserRepository struct{} 521 | 522 | func NewUserRepository() UserRepositoryInterface { 523 | repository := &UserRepository{} 524 | return repository 525 | } 526 | 527 | func (r *UserRepository) Exists(user entity.User) bool { 528 | 529 | // TODO: DBからuser.Idのレコードがあるか確認する処理 530 | exists := false 531 | 532 | return exists 533 | } 534 | ``` 535 | 536 | 具体的な処理内容は本質ではないため省略していますが、リポジトリはDBの操作が許されています。 537 | 538 | 実際はコンストラクタである `NewUserRepository` にORMやDBクライアントなどのインスタンスが渡され、`Exists` メソッドにその操作が定義されます。 539 | 540 | また、ユーザーを検索する `Find` や `Create` や `Delete` なども定義されるでしょう。 541 | 542 | 利用側であるドメインサービスの実装も見てみましょう。 543 | 544 | `./ddd/04repository/domain/domain-service/user.go` 545 | 546 | ```go 547 | package domainservice 548 | 549 | import ( 550 | entity "main/domain/entity" 551 | repository "main/domain/repository" 552 | ) 553 | 554 | func UserExists(user entity.User) bool { 555 | userRepository := repository.NewUserRepository() 556 | 557 | exists := userRepository.Exists(user) 558 | 559 | return exists 560 | } 561 | ``` 562 | 563 | ドメインサービスからDBの操作についての知識が剥がれました。 564 | 565 | 実のところ、ドメインサービスが依存しているのは `UserRepository` という実装ではなく `UserRepositoryInterface` という抽象(インターフェース)です。 566 | 567 | これは `依存性逆転の原則` というクリーンアーキテクチャにも共通するプラクティスです。 568 | 569 | インターフェースだけに依存すると利用側(ドメインサービス)は `Exists` という重複処理を持っていることしか知りませんが、フレームワークやデータベースなど外側の知識は何も知らなくて良くなります。 570 | 571 | これは最初のセクションで説明した `コアドメインに集中すること` に対しての回答にもなります。 572 | 573 | 例えばmysqlがredisに変わってもリポジトリインターフェースが持つ `Exists` の定義は変わることがありません。 574 | 575 | ドメインではなくアプリケーションの関心事であるリポジトリの処理を分けることで、以下を達成できます。 576 | 577 | - 環境が変わってもインターフェースは変わらず(ひいてはドメインを変えず)リポジトリの実装を変えるだけになる 578 | - テスト用にデータベースではなく変数に保持する `InMemoryUserRepository` を用意するなどが可能 579 | - DBを何にするかなど、外側の環境の意思決定を遅らせることができる 580 | 581 | *実際のところ、ドメインであるリポジトリはDBの知識が書かれてしまっています。今説明は端折りますが、ドメインが持つリポジトリの定義はリポジトリのInterfaceで、そのInterfaceの実装はドメイン外に定義されることになります。 582 | 583 | 584 | ## アプリケーションサービス 585 | 586 | ユースケースを実現するオブジェクトが `アプリケーションサービス` です。 587 | 588 | ユースケースが何か見てみましょう。以下は基本的な機能を持ったシステムのユースケース図です。 589 | 590 | ![](./part3_usecase.png) 591 | 592 | これらの処理を実装したのがアプリケーションサービスです。 593 | 594 | `ユーザー情報を確認する` に絞って実装を見てみましょう。 595 | 596 | まずはユーザーデータを取得する処理をリポジトリに追加実装しましょう。 597 | 598 | `./ddd/05application/domain/repository/user.go` 599 | 600 | ```go 601 | type UserRepositoryInterface interface { 602 | Find(name string) (*entity.User, error) 603 | } 604 | 605 | func (r *UserRepository) Find(name string) (*entity.User, error) { 606 | 607 | // TODO: DBからuser.nameを検索してuserオブジェクトを生成して返す 608 | userId, err := vo.NewUserId(1) 609 | if err != nil { 610 | return nil, err 611 | } 612 | userName, err := vo.NewUserName("Moriyuki Arakawa") 613 | if err != nil { 614 | return nil, err 615 | } 616 | user := entity.NewUser(*userId, *userName) 617 | 618 | return user, nil 619 | } 620 | ``` 621 | 622 | *具体的な処理内容は本質ではないため省略しています 623 | 624 | 次にアプリケーションサービスを実装します。 625 | 626 | `./ddd/05application/domain/application/user.go` 627 | 628 | ```go 629 | package application 630 | 631 | import ( 632 | entity "main/domain/entity" 633 | repository "main/domain/repository" 634 | ) 635 | 636 | type UserApplication struct { 637 | repository repository.UserRepositoryInterface 638 | } 639 | 640 | func NewUserApplication(repository repository.UserRepositoryInterface) *UserApplication { 641 | userApplication := &UserApplication{ 642 | repository: repository, 643 | } 644 | return userApplication 645 | } 646 | 647 | func (ua UserApplication) FindUser(name string) (*entity.User, error) { 648 | user, err := ua.repository.Find(name) 649 | 650 | if err != nil { 651 | return nil, err 652 | } 653 | 654 | return user, err 655 | } 656 | ``` 657 | 658 | アプリケーションサービスはリポジトリに実装された `Find` メソッドを利用してユーザー情報を検索して返します。 659 | 660 | ユースケースを実現するアプリケーションサービスが完成しました。 661 | 662 | 実際はフレームワークのハンドラなどに紐づけて利用されます。 663 | 664 | 665 | ## まとめ 666 | 667 | ここまでで根本的なDDDの3原則のうち `コアドメインに集中すること` の一部について学習しました。 668 | 669 | 他にも `ファクトリ` `集約` `仕様` 、他の原則にも `モデリング` や `ユビキタス言語` など学ぶことの多いテーマがあります。 670 | 671 | ぜひ本を読んでみてください。 672 | 673 | 674 | ## TODO: ドメインがなぜ重要なのかへのアンサー 675 | 676 | [世界一わかりやすいClean Architecture](https://www.nuits.jp/entry/easiest-clean-architecture-2019-09)の安定度と柔軟性のバランス配分 677 | 678 | ドメイン知識の分散を避けるため、外側に影響される必要のないドメインは依存される側として安定性が高くないといけない 679 | 680 | 681 | ## 参考 682 | 683 | - [エリック・エヴァンスのドメイン駆動設計](https://www.amazon.co.jp/dp/4798121967/) 684 | - [ドメイン駆動設計入門 成瀬允宣(著)](https://www.amazon.co.jp/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E5%85%A5%E9%96%80-%E3%83%9C%E3%83%88%E3%83%A0%E3%82%A2%E3%83%83%E3%83%97%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E3%81%AE%E5%9F%BA%E6%9C%AC-%E6%88%90%E7%80%AC-%E5%85%81%E5%AE%A3/dp/479815072X) 685 | - [ドメイン駆動設計 サンプルコード&FAQ](https://booth.pm/ja/items/3363104) 686 | - [DDD解説動画 little_hand_s DDD / Agile Channel](https://www.youtube.com/playlist?list=PLXMIJq1G-_65Z61i1iU6xSsj5TWNAIozd) 687 | - [DDD:ドメイン駆動設計 入門〜はじめの一歩](https://www.youtube.com/watch?v=03lDC8s0S5U) 688 | - [Domain-Driven Designのエッセンス 第1回 ドメイン駆動設計とは](https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap1.html) 689 | - [Domain-Driven Designのエッセンス 第2回 DDDの基礎と実践](https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap2.html) 690 | -------------------------------------------------------------------------------- /docs/part4.md: -------------------------------------------------------------------------------- 1 | # Clean Architecture 入門 2 | 3 | ## 概要 4 | 5 | - [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/Clean-Architecture-%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A8%AD%E8%A8%88-Robert-C-Martin/dp/4048930656) 6 | 7 | ### Clean Architecture とは 8 | 9 | 「関心の分離」を目的にしたノウハウやベストプラクティス集です。 10 | 11 | 本では実例を交えたレイヤーやデータフロー、クラス図などでの図解されています。 12 | 13 | 以下クリーンアーキテクチャの図が有名です。 14 | 15 | > ![](https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg) 16 | 17 | *[The Clean Architecture - The Clean Code Blog by Robert C. Martin(Uncle Bob)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)より引用 18 | 19 | 本の副題 `アーキテクチャのルールはどれも同じである` にもある通り、作者がいろいろなシステムを構築した経験で気づいたルールや、先人が残した知恵(原則など)を元に、システム開発でうまくいくプラクティスを理解/納得できるように歴史から紐解かれています。 20 | 21 | ### Clean Architecture の目的 22 | 23 | 「関心の分離」を目的にしていて、以下のようなアーキテクチャと同じ目的を持っています。 24 | 25 | - ヘキサゴナルアーキテクチャ 26 | - オニオンアーキテクチャ 27 | - DCIアーキテクチャ 28 | - BCE 29 | 30 | 「関心の分離」を目的にしていますが、そのメリットを見てみましょう。 31 | 32 | - フレームワーク非依存 33 | - テスト可能 34 | - UI非依存 35 | - データベース非依存 36 | - 外部エージェント非依存 37 | 38 | 噛み砕くと、 39 | 40 | - 時代ごとに進化していくフレームワークを入れ替え可能なツール(手段)として用いることでフレームワークへの依存をなくす。 41 | - それぞれのレイヤーごとにテストしやすくなる。 42 | - ビジネスルールを変更することなくウェブからアプリなどへ置き換えることができる。 43 | - ビジネスルールを変更することなくmysqlからredis、dynamoやインメモリ、その他データベースを置き換えることができる。 44 | - それらフレームワーク、UI、データベースがなんであるか知らなくても実装できる。(知らないように実装しないといけない) 45 | 46 | フレームワークもデータベースもUIも手段であって目的ではなく、本質にフォーカスすることができます。 47 | 48 | 外側を知らなくていいメリットはいろいろあり、年数がたつごとに古くなるツールを置き換えることが可能になったり、 49 | 技術選定を遅らせたり、知見がないために選定した技術では実現できないことが分かって戻りが発生した場合など、最小の労力で変更ができます。 50 | 51 | ### 今回のハンズオンで学ぶ範囲 52 | 53 | クリーンアーキテクチャの中心的な原則で一番重要な `SOLIDの原則` の内の1つ `依存性逆転の原則` について深掘りします。 54 | 55 | 依存方向をコントロールすることで「関心の分離」を達成し、目的でも語った得られるメリットを教授できるアーキテクチャについて学びます。 56 | 57 | ### 図の理解 58 | 59 | 簡単に図の表す意味を説明しておきます。 60 | 61 | - 円の4つの層はレイヤ 62 | - 矢印は依存方向 63 | - 右下のクラス図は詳細な依存方向と処理の流れ 64 | 65 | 現時点ではなんとなくでも結構です。次のセクションから似たアーキテクチャを理解しながら深ぼってみます。 66 | 67 | ### 似たアーキテクチャ 68 | 69 | クリーンアーキテクチャの詳細に入る前に、比較的わかりやすい似たアーキテクチャから簡単に確認してイメージしてみましょう。 70 | 71 | #### ヘキサゴナルアーキテクチャ 72 | 73 | > ![](https://alistair.cockburn.us/wp-content/uploads/2018/02/Hexagonal-architecture-complex-example.gif) 74 | 75 | *[Hexagonal architecture - by Alistair Cockburn](https://alistair.cockburn.us/hexagonal-architecture/)より引用 76 | 77 | ポートアンドアダプターとも呼ばれるヘキサゴナルアーキテクチャはアプリケーションとそれ以外を分け、それ以外の部分をつけ外しできるようにすることをコンセプトにしたアーキテクチャです。 78 | 79 | アプリケーションとそれ以外は、DDDのドメインとそれ以外と言い換えてもよく、[part3(DDD)の具体例](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/docs/part3.md#%E5%85%B7%E4%BD%93%E4%BE%8B)で見た図を思い出してもらえると分かりやすいかもしれません。 80 | 81 | ヘキサゴナルアーキテクチャはゲーム機で例えられることが多いです。 82 | 83 | - ゲーム機(アプリケーション) 84 | - 純正や非純正、アケコン、ガンコンなどの `コントローラー` 85 | - いろいろなカセット、 `ソフト` 86 | - 液晶、プラズマ、有機ELなど各 `ディスプレイ` 87 | - HDD、クラウド、メモリーカードなどの `記憶装置` 88 | 89 | ゲーム機をアプリケーションに見立て、全てポートとアダプターで繋ぐと拡張可能であることがイメージしやすいと思います。 90 | 91 | ゲーム機は(ディスプレイで言えばHDMIなどの)インターフェースに依存していて、将来どういうデバイスが作られるかなど、外側のことを知らずに作られています。 92 | 93 | ゲーム機(アプリケーション)は、使われる側で外側に依存しておらず、コントローラーやソフトやディスプレイや記憶装置はゲーム機に依存していることがわかると思います。 94 | 95 | クリーンアーキテクチャの図をもう一度見てみましょう。依存方向を表す矢印は常に内側に向いています。 96 | ヘキサゴナルも同様に、内側のアプリケーションにしか依存方向を伸ばさない、内側にしか依存してはいけないというクリーンアーキテクチャと共通する考え方です。 97 | 98 | フレームワークやデータベースも同様、ポートとアダプターというインターフェースに依存した作りにすることで拡張/付け替え可能なアーキテクチャで、 `依存性逆転の法則をベースに「関心の分離」を実現` しています。 99 | 100 | ちなみに六角形に特別な意味はなく、あやゆる方向にポートとアダプターを追加して拡張できるという表現のようです。 101 | 102 | #### オニオンアーキテクチャ 103 | 104 | > ![](https://i0.wp.com/jeffreypalermo.com/wp-content/uploads/2018/06/image257b0257d255b59255d.png?resize=366%2C259&ssl=1) 105 | 106 | *[The Onion Architecture : part 1 - by Jeffrey Palermo](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)より引用 107 | 108 | オニオンアーキテクチャはDDDで提唱されている考え方を図で表したアーキテクチャです。 109 | 110 | クリーンアーキテクチャの図に近づいてきました。 111 | 112 | オニオンアーキテクチャはDDDの章で学んだ用語が出てくるので理解しやすくなっているかと思います。 113 | 114 | 以下を依存される中心、アプリケーションコアとして、 115 | 116 | - アプリケーションサービス 117 | - ドメインサービス 118 | - ドメインモデル 119 | 120 | またそれ以外の以下を外側に配置しています。 121 | 122 | - ユーザーインターフェース 123 | - インフラストラクチャ 124 | - テスト 125 | 126 | ヘキサゴナルより実践的な例で、詳細に層を分けられています。 127 | 128 | ヘキサゴナルと同様に、 `ユーザーインターフェース` (WebかアプリかCLIか) や `インフラストラクチャ` (mysqlかredisか、はたまたECSかServerlessか) や `テスト` (jestかrspecか、はたまたunitかe2eか) はアプリケーションの関心の外であり、拡張/付け替え可能なアーキテクチャで、こちらも `依存性逆転の法則をベースに「関心の分離」を実現` しています。 129 | 130 | #### クリーンアーキテクチャは? 131 | 132 | > ![](https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg) 133 | 134 | *[The Clean Architecture - The Clean Code Blog by Robert C. Martin(Uncle Bob)](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)より引用 135 | 136 | 上記2つのアーキテクチャは共に `依存性逆転の法則をベースに「関心の分離」を実現` しています。クリーンアーキテクチャも同様です。 137 | 138 | 違いは層の名前や細かい具体例の有無です。クリーンアーキテクチャは細かいレイヤやクラス図などが詳細に書かれています。 139 | 140 | ### クリーンアーキテクチャについて 141 | 142 | 個人的に、クリーンアーキテクチャの理解を難しくしている一因だと思う勘違いしやすいことを先に説明しておきます。 143 | 144 | > ・4つの円だけ? 145 | > 146 | > 円は概要を示したものである。したがって、この4つ以外にも必要なものはあるだろう。この4つ以外は認めないというルールはない。 147 | > 148 | > ただし、依存性のルールは常に適用される。ソースコードの依存性は常に内側に向けるべきだ。 149 | > 150 | > 内側に近づけば、抽象度と方針のレベルは高まる。円の最も外側は、最下位レベルの具体的な詳細で構成される。 151 | > 152 | > 内側に近づくと、ソフトウェアは抽象化され、上位レベルの方針をカプセル化するようになる。円の最も内側は、最も一般的で、最上位レベルのものになる。 153 | 154 | * [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/Clean-Architecture-%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A8%AD%E8%A8%88-Robert-C-Martin/dp/4048930656) 22章 クリーンアーキテクチャ 202Pより引用 155 | 156 | 噛み砕くと 157 | 158 | - 図は例で、層の数や内容が重要というわけではない 159 | - 依存の方向は常に内側(ユースケースやエンティティ、DDDでいうドメイン)に向ける必要がある 160 | 161 | `依存性は常に内側に向けるべき` 理由や後半の以下について、`安定依存の原則(SDP)` から紐解いてみます。 162 | 163 | > 内側に近づけば、抽象度と方針のレベルは高まる。円の最も外側は、最下位レベルの具体的な詳細で構成される。 164 | > 内側に近づくと、ソフトウェアは抽象化され、上位レベルの方針をカプセル化するようになる。円の最も内側は、最も一般的で、最上位レベルのものになる。 165 | 166 | #### 安定依存の原則(SDP) 167 | 168 | `安定度の高い方向に依存する` という原則です。 169 | 170 | 逆に言えば `自分より不安定なコンポーネントに依存してはならない` ということになります。 171 | 172 | ##### 依存とは 173 | 174 | `安定度の高い方向に依存` の依存とは 175 | 176 | 例えば 177 | 178 | [レイヤードアーキテクチャ(4層アーキテクチャ)](https://qiita.com/little_hand_s/items/ebb4284afeea0e8cc752#%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%83%89%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3)における 179 | プレゼンテーションはアプリケーションに依存(利用)し知っていて、 180 | アプリケーションはドメインに依存(利用)し知っています。 181 | 182 | MVCにおける 183 | ViewはControllerが持っている変数やメソッドを知っていて依存していて、 184 | ControllerはModelが持っているプロパティやメソッドを知っていて依存しています。 185 | 186 | ##### 安定度とは 187 | 188 | `安定度の高い方向に依存` の安定度とは 189 | 190 | 高いほど依存(利用)されていて、低いほど利用されていない度合いです。 191 | 192 | ##### 安定度の高いコンポーネント 193 | 194 | ドメインやモデルは多数から利用されていて安定度の高いコンポーネントだと言えます。 195 | 196 | `安定度の高い方向に依存` した方が良い理由は、 197 | 198 | 多数のコードから依存(利用)される側のドメインやモデルは、本質的な価値を提供する「上位レベル」のコンポーネントなので、 199 | 外側であるプレゼンテーションやControllerやViewの変更の影響を受けないようにしようという理由です。 200 | 201 | ##### 安定度の低いコンポーネント 202 | 203 | 逆に依存(利用)する側のプレゼンテーションやViewは変更しやすく置き換えやすいことがわかるかと思います。 204 | 205 | 本質的な価値ではない「下位レベル」のコンポーネントは、変更しやすく置き換えやすいように依存(利用)する側として定義することができます。 206 | 207 | 変更しやすく置き換えやすいコンポーネントは柔軟性が高くなります。 208 | 209 | ##### 安定度と柔軟性のバランス配分 210 | 211 | `安定度の高い方向に依存` した方が良い理由を以下記事からもう少し深ぼってみます。 212 | 213 | [世界一わかりやすいClean Architecture](https://www.nuits.jp/entry/easiest-clean-architecture-2019-09) 214 | 215 | 216 | 上記記事の安定度と柔軟性のバランス配分の話にもある通り、 217 | 218 | `外側ほど柔軟性を高く(安定度は低い)` 作り、柔軟に変更できるようにする必要があり、 219 | 220 | `内側ほど安定度が高く(柔軟性は低い)` 作り、外側の変更に影響を受けないようにする必要がある、と書かれています。 221 | 222 | しかし、普通に作るとUsecase層がInfrastructure層に依存して、Infrastructure層が安定度が高くなってしまいます。 223 | 224 | Infrastructure層がUsecase層に依存する方向にしたい、それを実現するために依存方向をコントロールするのが、依存性逆転の原則で、クリーンアーキテクチャの中核になる原則です。 225 | 226 | ##### 柔軟性について 227 | 228 | 内側に求められるのが安定度だとわかりましたが、外側に求められる柔軟性について、 229 | 230 | 柔軟に変更できることの大切さについて柔軟性が悪くなる例を元に説明してみます。 231 | 232 | ###### ユーザー登録と更新フォームの例 233 | 234 | フロントエンドの `ユーザー登録フォーム` と `ユーザー情報更新フォーム` で説明します。 235 | 236 | 名前、ふりがな、メールアドレスなどのフォームを表示/編集できる必要があるフォームです。 237 | 238 | 登録も更新も全く同じなので、以下のような共通コンポーネントを作ることができます。 239 | 240 | ```tsx 241 | {}} 244 | /> 245 | ``` 246 | 247 | 後に以下のような要件が出てくるケースはよくあります。 248 | 249 | - ユーザー登録時に利用規約に同意する必要があるが、更新時フォームでは表示する必要はない 250 | - ユーザー登録時にパスワードが自動登録されるので、更新フォームで表示/編集できるようにする 251 | - 登録時の項目の多さでユーザーが離脱することがわかったので、登録時の必須項目は少なくしたい 252 | - ユーザー登録時と更新時のデザインを変えたい 253 | 254 | 簡単な要件であれば良いのですが、パラメータを受け取ったり受け取ったuserが空かなどを元に、内部に条件分岐を持って対応できたとします。 255 | 256 | また、後に以下の要件がでてくるケースがあります。 257 | 258 | - 管理者がユーザーの情報を更新する場合もあるので管理ページでも使う 259 | - 管理者は一部の項目のみ編集できる 260 | 261 | コンポーネント内部に条件分岐が溜まって複雑化していき、 262 | 年数が経つ頃には変更しにくい触りたくないコンポーネントになっているでしょう。 263 | 264 | 何が間違っていたのか、どういう対策をしたら良いのか次のセクションで説明します。 265 | 266 | ###### SOLID原則 単一責任の原則(SRP) 267 | 268 | `単一責任の原則` は1つの責任のみを果たすように設計するべきという考え方です。 269 | 270 | 上記例では `ユーザー登録` と `ユーザー情報更新` で偶然同じ項目を持っていたものの、ユースケースは別物です。 271 | 272 | `単一責任の原則` から答えを出すと、先ほどのコンポーネントは2つ以上の責任を持つことになるのでコンポーネントを分けることができます。 273 | 274 | ただ、初めからは気づきにくいことでもあるので、システムは変わるものだと捉えて、要件に合わせて分割するなどの決断が求められます。 275 | 276 | ```tsx 277 | {}} 279 | /> 280 | {}} 283 | /> 284 | ``` 285 | 286 | ###### DRY原則への違反? 287 | 288 | `DRY原則` (Don't Repeat Your Self)とは、繰り返しを避けるという原則です。 289 | 290 | とても重要な原則で、知っている人も多いのではないでしょうか? 291 | 292 | 繰り返しを避けるという意味で、先ほどの例で共通コンポーネントを作る方が正しいと捉える人も多いかと思います。 293 | 294 | DRY原則の正しい意味は `知識の重複を避ける` という意味で、ユースケースが別であるコードやテストなど例外がたくさん示されています。 295 | 296 | 達人プログラマーにも以下例で紹介されています。 297 | 298 | ``` 299 | def validate_age(value): 300 | validate_type(value, :integer) 301 | validate_min_integer(value, 0) 302 | 303 | def validate_quantity(value): 304 | validate_type(value, :integer) 305 | validate_min_integer(value, 0) 306 | ``` 307 | 308 | > ・コードのニ重化すべてが知識のニ重化というわけではない 309 | > 310 | > コードレビューの際に、これら2つの関数のコードは同じコードであるため、DRY原則に違反しているという声が上がりました。しかしその意見は間違っています。コードは同じですが、これらコードが表現している知識は異なっているのです。 311 | > 312 | > これら2つの関数は、異なる2つのものごとが同じ規則を有しているということを示しているだけです。それは偶然でありニ重化ではありません。 313 | 314 | [達人プログラマー P43](https://www.amazon.co.jp/%E9%81%94%E4%BA%BA%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC-%E7%AC%AC2%E7%89%88-%E7%86%9F%E9%81%94%E3%81%AB%E5%90%91%E3%81%91%E3%81%9F%E3%81%82%E3%81%AA%E3%81%9F%E3%81%AE%E6%97%85-David-Thomas/dp/4274226298/ref=sr_1_1?adgrpid=57017781007&hvadid=338576889005&hvdev=c&hvlocphy=1009798&hvnetw=g&hvqmt=e&hvrand=18366238437861830878&hvtargid=kwd-333592771130&hydadcr=15818_11177339&jp-ad-ap=0&keywords=%E9%81%94%E4%BA%BA%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC&qid=1660642754&sr=8-1)より引用 315 | 316 | また、クリーンアーキテクチャ本にも 317 | 318 | > モジュールはたったひとつのアクターに対して責任を負うべきである 319 | 320 | * [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/Clean-Architecture-%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A8%AD%E8%A8%88-Robert-C-Martin/dp/4048930656) 7章 SRP:単一責任の原則 82Pより引用 321 | 322 | とあるように、ユーザーや管理者など、使う人が変われば責任の種類も変わるので、コンポーネントをわけないと `単一責任の原則` に違反して複雑度が上がり、柔軟性が減る要因になってしまいます。 323 | 324 | 以下記事も参考として掲載しておきます。 325 | 326 | - [あなたはDRY原則を誤認している?](https://qiita.com/yatmsu/items/b4a84c4ae78fd67a364c) 327 | - DRY原則を破るということは、同じ知識を2箇所以上に記述することです。 328 | - DRYは素晴らしい考えですが、やり過ぎると密結合を生んでしまう。密結合が進むと修正時に影響範囲が増えてしまうんですね。 329 | - [リファクタリング自爆奥義集 共通化しちゃいけない箇所を共通化](https://qiita.com/MinoDriven/items/dac5505cf8442e1721d1#%E5%A5%A5%E7%BE%A9%EF%BC%93--%E5%85%B1%E9%80%9A%E5%8C%96%E3%81%97%E3%81%A1%E3%82%83%E3%81%84%E3%81%91%E3%81%AA%E3%81%84%E7%AE%87%E6%89%80%E3%82%92%E5%85%B1%E9%80%9A%E5%8C%96) 330 | - 同じようなロジック、似ているロジックであっても、概念が違えばDRYにすべきではないのです。 331 | - [DRYと不当な抽象化によるコストについて](https://postd.cc/on-dry-and-the-cost-of-wrongful-abstractions/) 332 | - 実際のところ、私は時として抽象化よりむしろ重複の方を勧めているのです。 333 | - [DHHはどのようにRailsのコントローラを書くのか](https://postd.cc/how-dhh-organizes-his-rails-controllers/) 334 | - 重複する方が、間違った方法で抽象化するよりずっと負担が小さくなります 335 | 336 | *全ての原則が常に正しいということではなく、プロジェクトによって目的やその他コンテキストが変わるのでバランスを考えて使いましょう。 337 | 338 | #### 依存性は常に内側に向けるべき 理由 339 | 340 | `内側ほど安定度が高く、外側ほど柔軟性を高く` 作る必要があることがわかったと思いますが、 341 | 話は戻って本にあるクリーンアーキテクチャの説明を再度見てみます。 342 | 343 | > 円の最も外側は、最下位レベルの具体的な詳細で構成される。 344 | 345 | というのは、外側は `ユーザーインターフェース` や `インフラストラクチャ` を指し、それらは変更しても影響を受けないよう、柔軟性高く入れ替えできるように依存する側として作るべき、ということです。 346 | 347 | > 内側に近づけば、抽象度と方針のレベルは高まる。 348 | > 内側に近づくと、ソフトウェアは抽象化され、上位レベルの方針をカプセル化するようになる。円の最も内側は、最も一般的で、最上位レベルのものになる。 349 | 350 | というのは、内側は `エンティティ` や `ユースケース` DDDでいう `ドメイン` を指し、 351 | 安定度の高いドメインはロジックを分散させず依存される側として作るべき、ということです。 352 | 353 | 依存性は常に内側に向け、内側ほど安定度が高く外側ほど柔軟性を高く作ることは、 `利口なUIアンチパターン` への対策にもつながります。 354 | 355 | ## クリーンアーキテクチャの図解 356 | 357 | 丸い図とデータフロー図、より詳細なクラス図があります。 358 | 359 | ひとつづつ詳細に見てみます。 360 | 361 | 4つのレイヤが例だと話した通り、それぞれの層や役割について必ずこうしろというものではありません。具体的な説明のために私の主観が混じっている点はご了承ください。 362 | 363 | ### 円 364 | 365 | ![](https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg) 366 | 367 | #### Enterprise Business Rules 368 | 369 | 中心の円が `Enterprise Business Rules` です。 370 | 371 | Enterprise Business Rulesは、最重要ビジネスルール、DDDにおける値オブジェクト、エンティティ、ドメインサービスなどを定義します。 372 | 373 | 主にデータ構造に焦点を当てて実装します。 374 | 375 | - Entiry 376 | - DDDの値オブジェクト 377 | - DDDのエンティティ 378 | - DDDのドメインサービス 379 | 380 | #### Application Business Rules 381 | 382 | 内側から2番目の層が `Application Business Rules` です。 383 | 384 | `Use Cases` とあるようにアプリケーション固有のビジネスルール、DDDにおけるアプリケーションサービスなどを定義します。 385 | 386 | この2番目の層までがドメインです。 387 | 388 | また、外側の層が依存するインターフェースも定義します。 389 | 390 | - UseCase 391 | - DDDのアプリケーションサービス 392 | - Repository Interface 393 | - Input Port 394 | - Output Port 395 | 396 | #### Interface Adapters 397 | 398 | 内側から3番目の層が `Interface Adapters` です。 399 | 400 | Webのリクエスト/レスポンスやDBに渡すためのデータ変換と永続化を定義します。 401 | 402 | - Controller 403 | - Presenter 404 | - Gateway 405 | 406 | #### Frameworks & Drivers 407 | 408 | 一番外側の層が `Frameworks & Drivers` です。 409 | 410 | ginでレスポンスを返すなど、フレームワークやデータベースについての具体的な処理を定義します。 411 | 412 | - DB 413 | - Web 414 | - UI 415 | - Devices 416 | - External Interfaces 417 | 418 | ### データフロー 419 | 420 | 右下の小さなクラス図について説明します。 421 | 422 | ![](https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg) 423 | 424 | - 緑は `Interface Adapters` のクラス 425 | - 赤は `Application Business Rules` のクラス 426 | - `` はインターフェース 427 | - 矢印は `依存` を表していて `A -> B` の場合、AがBを利用している関係 428 | - 白抜き矢印は `汎化` を表していて `A -> B` の場合、AがBを汎化/実装している関係 429 | - `Flow of controll` は制御の流れ 430 | 431 | これをまとめると以下の流れで処理されることになります。 432 | 433 | - `Controller` が `Use Case Input Port` のメソッドを呼ぶ 434 | - `Use Case Input Port` を実装している `Use Case Interactor` のメソッドが実行される 435 | - `Use Case Interactor` が `Use Case Output Port` のメソッドを呼ぶ 436 | - `Use Case Output Port` を実装している `Presenter` のメソッドが実行される 437 | 438 | なぜこういう流れにしているかというと、内側が外側に依存してしまうためです。 439 | 440 | 例えば、単純にInterfaceを使わず手続き的な流れで実装すると 441 | 442 | - `Controller` が `Use Case Interactor` のメソッドを呼ぶ 443 | - `Use Case Interactor` が `Presenter` のメソッドを呼ぶ 444 | 445 | となり `Controller` -> `Use Case Interactor` -> `Presenter` の流れは達成できているものの、依存性逆転の原則に違反してしまうからです。 446 | 447 | 先ほどのInterfaceの流れを通して、処理の流れを変えずに、依存方向をコントロールしています。 448 | 449 | 450 | ### 詳細なクラス図 451 | 452 | クリーンアーキテクチャ本 P204 に典型的なシナリオとして、より詳細なクラス図があります。 453 | 454 | ![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F293368%2Fd9071b69-e6ee-6f2b-dddd-6004707670b3.jpeg?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=83bbff110a3e41ad6ea0da0e1f8e860e) 455 | 456 | 具体的な処理の流れとしては以下になります。 457 | 458 | - ハンドラに紐づけられた `Controller` のメソッドを呼び出す 459 | - `Controller` がパラメータを `Input Data` 形式に変換し `Input Boundary` のメソッドに渡して呼び出す 460 | - `Input Boundary` を実装している `Use Case Interactor` のメソッドが実行される 461 | - `Use Case Interactor` は `Input Data` を元に `Data Access Interface` のメソッドを呼び出す 462 | - `Data Access Interface` を実装した `Data Access` がデータを返す 463 | - データを受け取った `Use Case Interactor` は `Output Data` 形式に変換し `Output Boundary` のメソッドに渡して呼び出す 464 | - `Output Boundary` を実装している `Presenter` のメソッドが実行され、データを表示する 465 | 466 | 常に依存は内側に向いていることがわかると思います。 467 | 468 | ## 実例 469 | 470 | https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture にTODOリストの例をサンプル実装しています。 471 | 472 | ```sh 473 | $ cd /path/to/go-clean-handson 474 | $ docker compose up 475 | 476 | $ mysql --host=127.0.0.1 --port=3306 --user=root --password=pass 477 | mysql> create database `ca-sample` default character set utf8mb4 collate utf8mb4_bin; 478 | 479 | $ cd /path/to/go-clean-handson/clean-architecture 480 | $ export DATABASE_URL='mysql://root:pass@tcp(localhost:3306)/ca-sample' 481 | $ migrate -database ${DATABASE_URL} -path migrations up 482 | 483 | $ curl --request GET --url http://localhost:8080/api/todos/1 484 | {"id":1,"name":"test"} 485 | ``` 486 | 487 | - [/main.go](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/main.go#L9)でginのルーターのセットアップとサーバー起動を行います 488 | - [/infrastructure/router/router.go](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/infrastructure/router/router.go#L25)でginのルーターとハンドラ(Controller)の紐付けを行います 489 | - [Controller](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/interface/controller/todo.go)は以下手順でJSONを出力します。 490 | - リクエスト情報を解析 491 | - [`InputPort`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_input_port.go#L9-L11) の望む [`InputData`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_input_port.go#L4-L6) 形式に変換 492 | - [`InputPort`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_input_port.go#L9-L11) の実装である [Interactor](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_interactor.go) のメソッドを呼び出し、以下手順で出力形式のデータを返す 493 | - [`Repository`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/repository/todo.go) を実装した [Repository](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/interface/repository/todo.go) のメソッドを利用して値を取得します。 494 | - さらに [`OutputPort`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_output_port.go#L15-L17) の望む [`OutputData`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_output_port.go#L4-L7) 形式に変換し [`OutputPort`](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/domain/application/usecase/todo_output_port.go#L15-L17) の実装である [Presenter](https://github.com/arakawamoriyuki/go-clean-handson/blob/main/clean-architecture/interface/presenter/todo.go) へ渡し、出力形式に変換して返します。 495 | - Interactorが返した値をJSONを出力 496 | 497 | *クリーンアーキテクチャ本の説明の通り、Presenterで表示可能な形式に変換していますが、テンプレートエンジンの必要ないAPIサーバーな都合上、Viewの概念を取り払い、Controllerに返して値を返すように変更しています。いくつかのリポジトリを参考にしていますが、レンダリングするパターンはPresenter/Controller両方あり、contextを引数でバケツリレーしないといけなかったり、レンダリングを片方にまとめたい理由からControllerに寄せる構成にしています。 498 | 499 | 今回はただのTODOアプリで過剰すぎる例ですが、アプリケーションが複雑な要件を持つにつれ効果を発揮していきます。 500 | 501 | また、細部の実装は違うものの、検索するとサンプルプロジェクトを見ることができます。 502 | 503 | それぞれ `依存性逆転の原則` を守っているものの定まった構成はなく、各のプロジェクトで必要なレイヤを必要な構成で組まれているようです。 504 | 505 | 参考にするといいと思います。 506 | 507 | - https://github.com/bxcodec/go-clean-arch 508 | - https://github.com/nrslib/CleanArchitecture/tree/master/CleanArchitectureSample 509 | - https://github.com/evrone/go-clean-template 510 | - https://zenn.dev/daiki_skm/articles/6ff48a9dc4f645 511 | - https://github.com/daiki-skm/clean-architecture-api 512 | - http://psychedelicnekopunch.com/archives/1308 513 | 514 | ## クリーンアーキテクチャのメリット/デメリット 515 | 516 | ### メリット 517 | 518 | - 疎結合 519 | - 依存性逆転の原則により、依存方向がルール化され、依存や前提、罠が減る 520 | - このコードを変更すると知らずに違う場所に影響ある、みたいなことが減る 521 | - 関心の分離 522 | - 無駄を削ぎ落としたイージー(簡単)ではなく、記述量は多いがわかりやすいシンプル(単純) 523 | - イージー(簡単)なコードは記述量は少ないが、細かい挙動の調整が効かなくて拡張もしにくい 524 | - シンプル(単純)なコードは愚直で記述量は多いが、保守・拡張がしやすく処理の見通しが良い 525 | - 変化に強い 526 | - 依存性逆転の原則により、DBやフレームワークの置き換えが可能 527 | - 置き換えが可能であり、技術選定を後回しにできる 528 | - 必要なら入れ替えられるので仮実装ができ、バックエンド、フロントエンドのような順で完成される必要がない。並行作業可能 529 | - テスタブル 530 | - 各層のコードの依存方向がルール化されているのでテストしやすい 531 | - リポジトリなどをテスト用に入れ替え可能 532 | 533 | ### デメリット 534 | 535 | - コードが冗長 536 | - シンプル(単純)なコードは愚直で記述量は多い 537 | - 直感的でない 538 | - 依存性逆転の原則を守る必要があり、手続き的なプログラミングが許されていない 539 | - 簡単に手続き的プログラミングで実装し、ルール違反できてしまう 540 | - 学習コスト高い 541 | - 少なくともクリーンアーキテクチャへの深い理解とメンバーへの説明/共有が必要だと思う 542 | - スキャフォールドのようなジェネレータ、テンプレを用意すると楽になりそう 543 | 544 | ## まとめ 545 | 546 | クリーンアーキテクチャを導入することによって何を達成したいのか、実装例とメリットデメリットを学びました。 547 | 548 | 実際に実装してみると、外側の変更は影響を考慮しなくてよく、内側の変更もコンパイルエラーとなって直すべき箇所がすぐわかるようになるなど色々なメリットがあったりします。 549 | 550 | 個人的には経験豊富な比較的規模の大きいプロジェクトに向いていると思いますが、逆にちゃんと理解したリーダーと、メンバーへの共有、スキャフォールドのようなジェネレータ、テンプレがしっかりしていれば、書くべきコードの場所がはっきりするシンプル(単純)なので初心者向きとの意見もあるようです。 551 | 552 | また、4層でなくてもいいというルール上、クリーンアーキテクチャの例の構成に必ずしも従う必要はなく、依存性逆転の原則を守りつつプロジェクトにあった最低限の構成(ドメインとそれ以外など)で関心の分離と冗長さのバランスを持って構成を考えてもいいと思います。 553 | 554 | 555 | ここまでで、アーキテクチャについての話が出た時に少しでも理解できる助けになっていれば嬉しいです。 556 | 557 | 必要なシーンが来た時に以下と相談して検討する選択肢として思い出してもらえればと思います。 558 | 559 | - チームの規模 560 | - メンバーのスキル 561 | - ソリューションの複雑さ 562 | - 時間や予算 563 | 564 | 入門ということでクリーンアーキテクチャほんの一部の紹介でしたが、紹介できていない原則やプラクティスがかなり多く、クリーンアーキテクチャを導入する/しないに関わらず参考になる話が多いので興味があれば読んでみてください。 565 | 566 | ## 参考 567 | 568 | - [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/Clean-Architecture-%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%A8%E8%A8%AD%E8%A8%88-Robert-C-Martin/dp/4048930656) 569 | - [実践クリーンアーキテクチャ](https://nrslib.com/clean-architecture/) 570 | - [Clean Architecture を毎日1章ずつ完読しました(PDF公開)](https://syobochim.hatenablog.com/entry/2022/06/06/125359) 571 | - [世界一わかりやすいClean Architecture](https://www.nuits.jp/entry/easiest-clean-architecture-2019-09) 572 | - [クリーンアーキテクチャ完全に理解した](https://gist.github.com/mpppk/609d592f25cab9312654b39f1b357c60) 573 | - [世界一わかりやすいClean Architecture](https://www.youtube.com/watch?v=pbCRHAM5NG0) 574 | - [実践クリーンアーキテクチャ 音ズレ修正Ver.【プログラミング】](https://www.youtube.com/watch?v=BvzjpAe3d4g) 575 | -------------------------------------------------------------------------------- /docs/part3_domain.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/part1.md: -------------------------------------------------------------------------------- 1 | # go 入門 2 | 3 | 参考 4 | 5 | - [はじめての Go](http://gihyo.jp/dev/feature/01/go_4beginners) 6 | - [Go Web プログラミング](https://astaxie.gitbooks.io/build-web-application-with-golang/content/ja/01.0.html) 7 | 8 | ```sh 9 | $ go version 10 | go version go1.17.7 darwin/amd64 11 | ``` 12 | 13 | ## 概要 14 | 15 | Go は、2009 年に Google により発表されたオープンソースのプログラミング言語です。 16 | 17 | - シンプルな言語仕様 18 | - Web、Windows、Mac、Linux、Android、iOS など環境に合わせた実行ファイルを生成できるクロスコンパイル 19 | - 標準のコーディング規約や静的解析系ツール、テストツールなどがある 20 | - 巨大なコードでも高速にコンパイルできて大規模開発にも適してる 21 | - 並行処理のサポート 22 | - 型やメソッドがあるが、継承はない。変わりに埋め込み型などがある 23 | - 例外の扱いが特殊 24 | 25 | ### シンプル 26 | 27 | 繰り返し構文は for 文のみです。 28 | 29 | また、三項演算子もありません。 30 | 31 | 表現のばらつきを抑えられ、同じような書き方になります。 32 | 33 | ### 危険の回避 34 | 35 | 意図せぬエラーの原因になる暗黙の型変換などは排除され、 36 | 37 | 使われていない変数、使われていないインポートはコンパイルエラーになります。 38 | 39 | ### 例外の排除 40 | 41 | `try/catch` がなく、発生した例外を戻り値として呼び出し側に返すことでハンドリングします。 42 | 43 | また、パニックとリカバという例外を制御する方法もあります。 44 | 45 | ### 他値を返す 46 | 47 | 複数の値を return できます。 48 | 49 | ### 平行処理 50 | 51 | ゴルーチンという軽量スレッドを用いて並行処理ができます。 52 | 53 | 同時に実行されているゴルーチンの間ではチャネルという機能でデータをやりとりできます。 54 | 55 | ### 標準パッケージ 56 | 57 | https://golang.org/pkg/ 58 | 59 | - `encoding/json` JSON から構造体、構造体から JSON に変換 60 | - `os`, `io` ファイルの作成、書き込み、読み込みなど 61 | - `io/ioutil` ファイルの操作のラッパー 62 | - `net/http` HTTP サーバ作成 API 63 | - `text/template` web に必要な html のテンプレートエンジン 64 | 65 | ### コマンド 66 | 67 | https://golang.org/doc/cmd 68 | 69 | | コマンド | 説明 | 70 | | :--------: | :---------------------------------: | 71 | | go build | プログラムのビルド | 72 | | go run | プログラムのビルドと実行 | 73 | | go mod | パッケージ管理 | 74 | | go install | プログラムのビルドとインストール | 75 | | go get | 外部パッケージの取得 | 76 | | go fmt | Go の規約に合わせてプログラムを整形 | 77 | | go fix | 古いコードを書き換える | 78 | | go vet | 静的解析 | 79 | | go test | テストやベンチマークの実行 | 80 | | go doc | ソースからドキュメントの生成 | 81 | 82 | --- 83 | 84 | ## 動かしてみる 85 | 86 | ### インストール 87 | 88 | `brew install go` や `goenv` 、もしくは[インストーラー](https://golang.org/doc/install)でインストールできます。 89 | 90 | ### Hello world 91 | 92 | `main.go` 93 | 94 | ```go 95 | package main 96 | 97 | import "fmt" 98 | 99 | func main(){ 100 | fmt.Printf("Hello, world") 101 | } 102 | ``` 103 | 104 | ```sh 105 | $ go run main.go 106 | hello world 107 | ``` 108 | 109 | ### ビルドしてバイナリを実行してみる 110 | 111 | ```sh 112 | $ go build main.go 113 | $ ls 114 | main main.go 115 | $ ./main 116 | hello world 117 | ``` 118 | 119 | 各環境で動くバイナリができます。 120 | 121 | 他の環境で build したい場合は `GOOS=linux` や `GOARCH=amd64` など環境変数で指定してビルドできます。 122 | 123 | ### コードフォーマット 124 | 125 | 標準のコーディング規約とフォーマッターが付属されていて、以下コマンドでフォーマットすることができます。 126 | 127 | ```sh 128 | $ go fmt main.go 129 | ``` 130 | 131 | vscode では以下設定で保存時に自動フォーマットできます。 132 | 133 | ```json 134 | { 135 | "[go]": { 136 | "editor.formatOnSave": true 137 | } 138 | } 139 | ``` 140 | 141 | --- 142 | 143 | ## プロジェクト構成 144 | 145 | GOPATH モード と モジュール対応モード があります。 146 | 147 | GOPATH モード は GOPATH 環境変数からの相対パスで構成します。 148 | 149 | モジュール対応モード は任意のディレクトリで開発・ビルドが可能なモードです。 150 | 151 | 1.17 から標準なモジュール対応モードで説明します。 152 | 153 | ### サブモジュールと外部モジュール 154 | 155 | まずはモジュール名を決めて `go mod init` します。 156 | 157 | モジュール名は `{Repository FQDN}/{Repository Path}/{Module Path}` のような命名規則(例えば `github.com/arakawamoriyuki/go-clean-handson/test` など)にすると、誰でも利用できるようになります。 158 | 159 | 公開しない場合は `main` などでも問題ありません。 160 | 161 | ```sh 162 | $ go mod init main 163 | or 164 | $ go mod init github.com/arakawamoriyuki/go-clean-handson/test 165 | ``` 166 | 167 | 自作したサブモジュール、外部のモジュールを読み込んで使ってみましょう。 168 | 169 | 必要なパッケージがあれば以下のように追加することができます。 170 | 171 | ```sh 172 | $ go get github.com/arakawamoriyuki/gosample 173 | ``` 174 | 175 | 以下のような構成でサブパッケージと外部パッケージの利用例をみてみましょう。 176 | 177 | ```sh 178 | $ tree 179 | ├── go.mod 180 | ├── go.sum 181 | ├── main.go 182 | └── subpkg 183 | ├── other.go 184 | └── subpkg.go 185 | ``` 186 | 187 | `subpkg/subpkg.go` 188 | 189 | ```go 190 | package subpkg 191 | 192 | var Message string = "subpkg/subpkg.go message" 193 | ``` 194 | 195 | `subpkg/other.go` 196 | 197 | ```go 198 | package subpkg 199 | 200 | var OtherMessage string = "subpkg/other.go message" 201 | ``` 202 | 203 | `main.go` 204 | 205 | ```go 206 | package main 207 | 208 | import ( 209 | "fmt" 210 | 211 | "main/subpkg" 212 | 213 | "github.com/arakawamoriyuki/gosample" 214 | ) 215 | 216 | func main() { 217 | fmt.Println(subpkg.Message) 218 | fmt.Println(subpkg.OtherMessage) 219 | fmt.Println(gosample.Message) 220 | } 221 | ``` 222 | 223 | 早速実行してみます。 224 | 225 | ```sh 226 | $ go run main.go 227 | ``` 228 | 229 | 同一パッケージの関数の場合はファイル名関係なく、関数名や変数名のみで呼べ出せます。 230 | 231 | ファイル名は関係なく、 `subpkg/other.go` は `subpkg` パッケージなので呼び出せていることにも注目しましょう。 232 | 233 | 参考: https://dev.classmethod.jp/articles/go-setup-and-sample/ 234 | 235 | また、以下でビルドすることができ、そのディレクトリに実行ファイルを作成できます。 236 | 237 | ```sh 238 | $ go build 239 | $ ./main 240 | ``` 241 | 242 | ### 命名規則 243 | 244 | - ファイル名 245 | - スネークケース `snake_case.go` 246 | - ディレクトリ名 247 | - 小文字英字のみ `sample/` 248 | - 必要ならケバブケース `sample-module/` 249 | - メソッド 250 | - エクスポートする関数やメソッド(public)はアッパーキャメルケース `UpperCamelCase` 251 | - ファイル内でしか利用しない関数やメソッド(private)はローワーキャメルケース `lowerCamelCase` 252 | - レシーバー名 253 | - 省略した英語 1 文字か 2 文字など短くつける多い HttpClient は `c` など 254 | - 変数、引数名 255 | - 英語 1 文字か 2 文字でも良いが、可読性を考えて長くても良い `httpClient` など 256 | - 参考 257 | - https://zenn.dev/keitakn/articles/go-naming-rules 258 | - https://github.com/kubernetes/kubernetes/tree/master/api 259 | 260 | --- 261 | 262 | ## 言語仕様 263 | 264 | ### パッケージ宣言 265 | 266 | ```go 267 | package main 268 | ``` 269 | 270 | `go run` では main パッケージの main 関数が実行されます。 271 | 272 | サブモジュールを作成する場合はディレクトリ名に合わせてつけてください。 273 | 274 | ### インポート 275 | 276 | 外部モジュールはリポジトリ含めた絶対パス、自作のサブモジュールなどはモジュール名に合わせて変更します。 277 | 278 | 区切りにカンマは必要ありません。 279 | 280 | ```go 281 | import ( 282 | # 組み込み関数 283 | "fmt" 284 | 285 | # サブモジュール (モジュール名がmainの場合) 286 | "main/subpkg" 287 | 288 | # サブモジュール (モジュール名がリポジトリ含めた絶対パスの場合) 289 | "github.com/arakawamoriyuki/go-clean-handson/test/subpkg" 290 | 291 | # 外部モジュール 292 | "github.com/arakawamoriyuki/gosample" 293 | ) 294 | ``` 295 | 296 | インポートにはいくつかのオプションがあります。 297 | 298 | - オプションなしの場合はモジュール名を指定して使える `fmt.Println()` 299 | - `文字列` はエイリアスになる( `fmt.Println()` が `f.Println()` ) 300 | - `.` は中の関数が展開される ( `strings.ToUpper()` が `ToUpper()` ) 301 | - `_` は使用していないパッケージだと明示する (使用してなくてもコンパイルエラーにならない) 302 | 303 | ```go 304 | import ( 305 | "fmt" 306 | f "fmt" 307 | . "strings" 308 | _ "github.com/wdpress/gosample" 309 | ) 310 | ``` 311 | 312 | ### 型 313 | 314 | | 型 | 説明 | 315 | | :--------: | :---------------------------: | 316 | | uint8 | 8 ビット符号なし整数 | 317 | | uint16 | 16 ビット符号なし整数 | 318 | | uint32 | 32 ビット符号なし整数 | 319 | | uint64 | 64 ビット符号なし整数 | 320 | | int8 | 8 ビット符号あり整数 | 321 | | int16 | 16 ビット符号あり整数 | 322 | | int32 | 32 ビット符号あり整数 | 323 | | int64 | 64 ビット符号あり整数 | 324 | | float32 | 32 ビット浮動小数 | 325 | | float64 | 64 ビット浮動小数 | 326 | | complex64 | 64 ビット複素数 | 327 | | complex128 | 128 ビット複素数 | 328 | | byte | uint8 のエイリアス | 329 | | rune | Unicode のコードポイント | 330 | | uint | 32 か 64 ビットの符号なし整数 | 331 | | int | 32 か 64 ビットの符号あり整数 | 332 | | uintptr | ポインタ値用符号なし整数 | 333 | | error | エラーを表わすインタフェース | 334 | 335 | 文字列はダブルクォートで書くことができます。 336 | 337 | ```go 338 | var Message string = "hello world" 339 | ``` 340 | 341 | ヒアドキュメントはバッククォートで書くことができます。 342 | 343 | ```go 344 | var Message string = `first line 345 | second line 346 | third line` 347 | ``` 348 | 349 | また、コンパイラが明らかにわかる型を推論して型宣言を省略することもできます。 350 | 351 | ```go 352 | message := "hello world" 353 | ``` 354 | 355 | ### 変数 356 | 357 | 変数は `var 変数名 型 = 値` のような形式で定義できます。 358 | 359 | また、複数定義することもでき、同じ型なら省略可能です。 360 | 361 | ```go 362 | var message string = "hello world" 363 | 364 | var foo, bar, buz string = "foo", "bar", "buz" 365 | var ( 366 | a string = "aaa" 367 | b = "bbb" 368 | c = "ccc" 369 | d = "ddd" 370 | e = "eee" 371 | ) 372 | ``` 373 | 374 | ### 定数 375 | 376 | 定数は const で定義でき、再代入不可になります。 377 | 378 | ```go 379 | const Hello string = "hello" 380 | Hello = "bye" // cannot assign to Hello 381 | ``` 382 | 383 | ### ゼロ値 384 | 385 | 代入しない場合、変数はゼロ値で初期化されます。 386 | 387 | ```go 388 | var i int // 整数型のゼロ値 0 になる 389 | ``` 390 | 391 | | 型 | ゼロ値 | 392 | | :----------: | :--------------------------: | 393 | | 整数型 | 0 | 394 | | 浮動小数点型 | 0.0 | 395 | | bool | false | 396 | | string | "" | 397 | | 配列 | 各要素がゼロ値の配列 | 398 | | 構造体 | 各フィールドがゼロ値の構造体 | 399 | | そのほかの型 | nil | 400 | 401 | ### if 402 | 403 | 条件に丸括弧は必要ありません。また、三項演算子はありません。 404 | 405 | ```go 406 | a, b := 10, 100 407 | if a > b { 408 | fmt.Println("a is larger than b") 409 | } else if a < b { 410 | fmt.Println("a is smaller than b") 411 | } else { 412 | fmt.Println("a equals b") 413 | } 414 | ``` 415 | 416 | ### for 417 | 418 | for を利用してループを回すことができます。 419 | 420 | ```go 421 | for i := 0; i < 10; i++ { 422 | fmt.Println(i) 423 | } 424 | ``` 425 | 426 | while のような書き方も可能です。 427 | 428 | ```go 429 | n := 0 430 | for n < 10 { 431 | fmt.Printf("n = %d\n", n) 432 | n++ 433 | } 434 | ``` 435 | 436 | また、以下のように無限ループを作ることもできます。 437 | 438 | ```go 439 | for { 440 | doSomething() 441 | } 442 | ``` 443 | 444 | ループを終了する `break` 、スキップする `continue` なども利用可能です。 445 | 446 | 配列は range を使ってループすることができます。 447 | 448 | ```go 449 | for i, v := range []string{"a", "b", "c"} { 450 | fmt.Println(i, v) 451 | } 452 | ``` 453 | 454 | ### switch 455 | 456 | 以下のようにカンマで区切った複数の値も指定可能です。 457 | 458 | 条件に一致した処理を走らせることができます。 459 | 460 | ```go 461 | n := 10 462 | switch n { 463 | case 15: 464 | fmt.Println("FizzBuzz") 465 | case 5, 10: 466 | fmt.Println("Buzz") 467 | case 3, 6, 9: 468 | fmt.Println("Fizz") 469 | default: 470 | fmt.Println(n) 471 | } 472 | ``` 473 | 474 | 言語によっては `break` がない場合次の case も評価される言語もありますが、 475 | 476 | golang では 1 つの case 実行されると次の case に移ることはありません。 477 | 478 | `fallthrough` キーワードで次にうつる事もできます。 479 | 480 | ```go 481 | n := 3 482 | switch n { 483 | case 3: 484 | n = n - 1 485 | fallthrough 486 | case 2: 487 | n = n - 1 488 | fallthrough 489 | case 1: 490 | n = n - 1 491 | fmt.Println(n) // 0 492 | } 493 | ``` 494 | 495 | ### 関数 496 | 497 | 関数は `func` で作ります。引数には型を指定、複数同じ型なら一つにまとめることもできます。 498 | 499 | ```go 500 | func sum(i, j int) { 501 | fmt.Println(i + j) 502 | } 503 | ``` 504 | 505 | また、戻り値は関数定義のあとに型を指定します。 506 | 507 | ```go 508 | func sum(i, j int) int { 509 | return i + j 510 | } 511 | ``` 512 | 513 | 複数値を返す場合、複数の型を指定します。 514 | 515 | ```go 516 | func swap(i, j int) (int, int) { 517 | return j, i 518 | } 519 | ``` 520 | 521 | 名前付き戻り値で `return` の後の値を省略することもできます。(代入された値を返す。代入されていなければ結果的にゼロ値を返す) 522 | 523 | ```go 524 | func div(i, j int) (result int, err error) { 525 | if j == 0 { 526 | err = errors.New("divied by zero") 527 | return // return 0, errと同じ 528 | } 529 | result = i / j 530 | return // return result, nilと同じ 531 | } 532 | ``` 533 | 534 | 以下のように無名関数を作ることもできます。 535 | 536 | ```go 537 | func(i, j int) { 538 | fmt.Println(i + j) 539 | }(2, 4) 540 | ``` 541 | 542 | 関数を変数に代入できます。 543 | 544 | ```go 545 | var sum func(i, j int) = func(i, j int) { 546 | fmt.Println(i + j) 547 | } 548 | ``` 549 | 550 | 可変長引数も利用できます。 551 | 552 | ```go 553 | func sum(nums ...int) (result int) { 554 | // numsは[]int型 555 | for _, n := range nums { 556 | result += n 557 | } 558 | return 559 | } 560 | 561 | func main() { 562 | fmt.Println(sum(1, 2, 3, 4)) // 10 563 | } 564 | ``` 565 | 566 | --- 567 | 568 | ### エラー 569 | 570 | go は `try/catch` や `throw` がありません。 571 | 572 | 変わりにエラーを戻り値で返すことでハンドリングします。 573 | 574 | また、エラーの作成は errors パッケージを使います。 575 | 576 | ```go 577 | package main 578 | 579 | import ( 580 | "errors" 581 | "fmt" 582 | "log" 583 | ) 584 | 585 | func div(i, j int) (int, error) { 586 | if j == 0 { 587 | // 自作のエラーを返す 588 | return 0, errors.New("divied by zero") 589 | } 590 | return i / j, nil 591 | } 592 | 593 | func main() { 594 | n, err := div(10, 0) 595 | if err != nil { 596 | // エラーを出力しプログラムを終了する 597 | log.Fatal(err) 598 | } 599 | fmt.Println(n) 600 | } 601 | ``` 602 | 603 | 複数の値を返す場合はエラーを最後にする慣習があります。 604 | 605 | ## 配列 606 | 607 | 配列は固定長で長さを指定します。 608 | 609 | ```go 610 | var arr1 [4]string 611 | ``` 612 | 613 | `[...]` で暗黙的に長さの指定ができます。 614 | 615 | ```go 616 | arr := [...]string{"a", "b", "c", "d"} 617 | ``` 618 | 619 | 引数で受け取る場合にも型と長さの指定をする必要があります。 620 | 621 | ```go 622 | func fn(arr [4]string) { 623 | fmt.Println(arr) 624 | } 625 | 626 | func main() { 627 | var arr1 [4]string 628 | var arr2 [5]string 629 | 630 | fn(arr1) // ok 631 | fn(arr2) // コンパイルエラー 632 | } 633 | ``` 634 | 635 | スライスという可変長配列も定義できます。 636 | 637 | ```go 638 | var s []string 639 | s := []string{"a", "b", "c", "d"} 640 | ``` 641 | 642 | 値を部分的に切り出す事ができます。 643 | 644 | ```go 645 | s := []int{0, 1, 2, 3, 4, 5} 646 | fmt.Println(s[2:4]) // [2 3] 647 | fmt.Println(s[0:len(s)]) // [0 1 2 3 4 5] 648 | fmt.Println(s[:3]) // [0 1 2] 649 | fmt.Println(s[3:]) // [3 4 5] 650 | fmt.Println(s[:]) // [0 1 2 3 4 5] 651 | ``` 652 | 653 | ### append 654 | 655 | `append` はスライスの末尾に値を追加し、その結果を返す組込み関数です。 656 | 657 | ```go 658 | s1 := []string{"a", "b"} 659 | s1 = append(s1, "c") // s1にs2を追加 660 | fmt.Println(s1) // [a b c] 661 | ``` 662 | 663 | また、可変長の値を受け取ることもできます。 664 | 665 | ```go 666 | s1 := []string{"a", "b"} 667 | s2 := []string{"c", "d"} 668 | s1 = append(s1, s2...) // s1にs2を追加 669 | fmt.Println(s1) // [a b c d] 670 | ``` 671 | 672 | ### range 673 | 674 | 添字によるアクセスの代わりに `range` を使用できます。 675 | 676 | ```go 677 | s1 := []string{"a", "b", "c", "d"} 678 | 679 | for index, value := range s1 { 680 | // index = 添字, value = 値 681 | fmt.Println(index, value) 682 | // 0 a 683 | // 1 b 684 | // 2 c 685 | // 3 d 686 | } 687 | ``` 688 | 689 | また、map 型もループで回せます。 690 | 691 | ```go 692 | months := map[string]int{ 693 | "January": 1, 694 | "February": 2, 695 | } 696 | 697 | for key, value := range months { 698 | fmt.Println(key, value) 699 | // January 1 700 | // February 2 701 | } 702 | ``` 703 | 704 | ## マップ 705 | 706 | `string` のキーに `int` の値を格納するマップ 707 | 708 | ```go 709 | months := map[string]int{ 710 | "January": 1, 711 | "February": 2, 712 | } 713 | ``` 714 | 715 | キーの存在確認は以下のように判定できます。 716 | 717 | ```go 718 | _, ok := months["January"] 719 | if ok { 720 | // データがあった場合 721 | } 722 | ``` 723 | 724 | マップからデータを消す場合は delete を使います。 725 | 726 | ```go 727 | delete(months, "January") 728 | ``` 729 | 730 | ## ポインタ 731 | 732 | Go はポインタを扱うことができます。 733 | 734 | アンパサンド(&)はアドレス演算子。値からポインタへの変換を行う。 735 | 736 | アスタリスク(\*)は間接参照演算子。ポインタから値への変換を行う。 737 | 738 | int などでも参照渡しできるし配列を値渡しもできます。 739 | 740 | ```go 741 | func callByValue(i int) { 742 | i = 20 // 代入しても呼び出し側へ影響しない 743 | } 744 | 745 | func callByRef(i *int) { 746 | *i = 20 // 代入すると呼び出し側の変数も変わる 747 | } 748 | 749 | func main() { 750 | var i int = 10 751 | callByValue(i) // 値を渡す 752 | fmt.Println(i) // 10 753 | callByRef(&i) // アドレスを渡す 754 | fmt.Println(i) // 20 755 | } 756 | ``` 757 | 758 | 引数受ける側も関数利用側もポインタ渡す/受けるなら `*` や `&` つけないとコンパイルエラーになります。 759 | 760 | ## defer 761 | 762 | ファイル開いたり、データベースにコネクション貼った場合など、エラーが起きても起きなくても実行して欲しい処理に使います。 763 | 764 | `defer` は延期という意味。他の言語でいう `finaly` のような使い方をします。 765 | 766 | ```go 767 | func main() { 768 | file, err := os.Open("./error.go") 769 | if err != nil { 770 | // エラー処理 771 | } 772 | // 関数を抜ける前に必ず実行される 773 | defer file.Close() 774 | // 正常処理 775 | } 776 | ``` 777 | 778 | ## パニック 779 | 780 | エラーは戻り値によって表現するのが基本ですが、 781 | 782 | 配列やスライスの範囲外にアクセスした場合や、ゼロ除算をしてしまった場合などはエラーを返せません。 783 | 784 | この状態をパニックという。 785 | 786 | パニックで発生したエラーは `recover` で拾えるので、defer で処理する事でエラー処理ができます。 787 | 788 | ```go 789 | func main() { 790 | defer func() { 791 | err := recover() 792 | if err != nil { 793 | // runtime error: index out of range 794 | log.Fatal(err) 795 | } 796 | }() 797 | 798 | a := []int{1, 2, 3} 799 | fmt.Println(a[10]) // パニックが発生 800 | } 801 | ``` 802 | 803 | パニックは自分で起こす事もできます。 804 | 805 | ```go 806 | a := []int{1, 2, 3} 807 | for i := 0; i < 10; i++ { 808 | if i >= len(a) { 809 | panic(errors.New("index out of range")) 810 | } 811 | fmt.Println(a[i]) 812 | } 813 | ``` 814 | 815 | --- 816 | 817 | ## type 818 | 819 | `type` キーワードを使って独自の型を作ることができます。 820 | 821 | 一例を見てみましょう。 822 | 823 | 以下の関数は int 型の `id` と `priority` を受け取ります。 824 | 825 | ```go 826 | func ProcessTask(id, priority int) { 827 | } 828 | ``` 829 | 830 | 同じ `int` 型なので 831 | 832 | 引数の順番間違えてもコンパイルが通り、間違えやすいインターフェースになっています。 833 | 834 | ```go 835 | var id int = 3 836 | var priority int = 5 837 | ProcessTask(id, priority) 838 | ProcessTask(priority, id) // 順番間違えてもコンパイル通る 839 | ``` 840 | 841 | 場合に応じて独自の型を定義すると間違いが減り、安全になります。 842 | 843 | ```go 844 | type ID int 845 | type Priority int 846 | 847 | func ProcessTask(id ID, priority Priority) { 848 | } 849 | 850 | var id ID = 3 851 | var priority Priority = 5 852 | ProcessTask(priority, id) // コンパイルエラー 853 | ``` 854 | 855 | ## 構造体(struct) 856 | 857 | 構造体を独自の型として宣言できます。 858 | 859 | また、構造体のプロパティはドットでアクセス可能です。 860 | 861 | ```go 862 | type Task struct { 863 | ID int 864 | Detail string 865 | Done bool 866 | } 867 | 868 | var task Task = Task{ 869 | ID: 1, 870 | Detail: "buy the milk", 871 | Done: true, 872 | } 873 | fmt.Println(task.ID) // 1 874 | fmt.Println(task.Detail) // "buy the milk" 875 | fmt.Println(task.Done) // true 876 | ``` 877 | 878 | ## ポインタ型 879 | 880 | ```go 881 | var task Task = Task{} // Task型 882 | var task *Task = &Task{} // Taskのポインタ型 883 | ``` 884 | 885 | ポインタ型ではない型は値渡しされます。 886 | 887 | ```go 888 | type Task struct { 889 | ID int 890 | Detail string 891 | Done bool 892 | } 893 | 894 | func Finish(task Task) { 895 | task.Done = true 896 | } 897 | 898 | func main() { 899 | task := Task{Done: false} 900 | Finish(task) 901 | fmt.Println(task.Done) // falseのまま 902 | } 903 | ``` 904 | 905 | ポインタ型は参照渡しされます。 906 | 907 | ```go 908 | func Finish(task *Task) { 909 | task.Done = true 910 | } 911 | 912 | func main() { 913 | task := &Task{Done: false} 914 | Finish(task) 915 | fmt.Println(task.Done) // true 916 | } 917 | ``` 918 | 919 | ## new 920 | 921 | 構造体は組み込み関数 `new` を使い、ゼロ値で初期化できます。 922 | 923 | ```go 924 | type Task struct { 925 | ID int 926 | Detail string 927 | Done bool 928 | } 929 | 930 | task := new(Task) 931 | fmt.Println(task.ID == 0) // true 932 | fmt.Println(task.Detail == "") // true 933 | fmt.Println(task.Done == false) // true 934 | ``` 935 | 936 | ## コンストラクタ 937 | 938 | Go にはコンストラクタにあたる構文がありません。 939 | 940 | 代わりに New で始まる関数を定義し、その内部で構造体を生成するのが通例です。 941 | 942 | ```go 943 | func NewTask(id int, detail string) *Task { 944 | task := &Task{ 945 | ID: id, 946 | Detail: detail, 947 | Done: false, 948 | } 949 | return task 950 | } 951 | 952 | func main() { 953 | task := NewTask(1, "buy the milk") 954 | // &{ID:1 Detail:buy the milk Done:false} 955 | fmt.Printf("%+v", task) 956 | } 957 | ``` 958 | 959 | ## メソッド 960 | 961 | メソッドはメソッド名の前に定義したい型を指定します。 962 | 963 | ```go 964 | func (変数名 メソッドを定義したい型) メソッド名() 戻り値の型 { 965 | } 966 | ``` 967 | 968 | ```go 969 | package main 970 | 971 | import ( 972 | "fmt" 973 | ) 974 | 975 | type Task struct { 976 | ID int 977 | Detail string 978 | Done bool 979 | } 980 | 981 | func NewTask(id int, detail string) *Task { 982 | task := &Task{ 983 | ID: id, 984 | Detail: detail, 985 | Done: false, 986 | } 987 | return task 988 | } 989 | 990 | // taskの文字列表現を返す 991 | func (task Task) String() string { 992 | str := fmt.Sprintf("%d) %s %t", task.ID, task.Detail, task.Done) 993 | return str 994 | } 995 | 996 | // taskを完了する 997 | func (task *Task) Finish() { 998 | task.Done = true 999 | } 1000 | 1001 | func main() { 1002 | task := NewTask(1, "buy the milk") 1003 | fmt.Println(task.String()) // 1) buy the milk false 1004 | task.Finish() 1005 | fmt.Println(task.String()) // 1) buy the milk true 1006 | } 1007 | ``` 1008 | 1009 | ## インターフェース 1010 | 1011 | `type インターフェース名 interface {}` でメソッドが定義されている事を強制する用途に利用します。 1012 | 1013 | ```go 1014 | package main 1015 | 1016 | import ( 1017 | "fmt" 1018 | ) 1019 | 1020 | type Task struct { 1021 | ID int 1022 | Detail string 1023 | Done bool 1024 | } 1025 | 1026 | func NewTask(id int, detail string) *Task { 1027 | task := &Task{ 1028 | ID: id, 1029 | Detail: detail, 1030 | Done: false, 1031 | } 1032 | return task 1033 | } 1034 | 1035 | func (task Task) String() string { 1036 | str := fmt.Sprintf("%d) %s %t", task.ID, task.Detail, task.Done) 1037 | return str 1038 | } 1039 | 1040 | // Stringerインターフェースを定義 1041 | type Stringer interface { 1042 | String() string 1043 | } 1044 | 1045 | // この関数には.String()メソッドを実装しているオブジェクトを渡せる。 1046 | func print(stringer Stringer) { 1047 | fmt.Println(stringer.String()) 1048 | } 1049 | 1050 | func main() { 1051 | task := NewTask(1, "buy the milk") 1052 | 1053 | // TaskはString()実装しているので渡せる 1054 | print(task) 1055 | 1056 | // Stringer型の変数iを定義できる 1057 | var s Stringer 1058 | s = task 1059 | fmt.Println(s) 1060 | 1061 | // Stringer型の配列に入れることもできる 1062 | stringers := []Stringer{ 1063 | task, 1064 | NewTask(2, "buy the banana"), 1065 | NewTask(3, "buy the pan"), 1066 | } 1067 | fmt.Println(stringers) 1068 | } 1069 | ``` 1070 | 1071 | 実際は別の型であっても、インターフェースの指定の通りの実装されていれば置き換えることができます。 1072 | 1073 | ### interface{} 1074 | 1075 | すべての引数を受け付ける Any 型が作れる 1076 | 1077 | ```go 1078 | type Any interface { 1079 | } 1080 | 1081 | func Do(any Any) { 1082 | // do something 1083 | } 1084 | ``` 1085 | 1086 | 書き方自体以下と同じです。 1087 | 1088 | ```go 1089 | func Do(any interface{}) { 1090 | // do something 1091 | } 1092 | ``` 1093 | 1094 | Tips: v1.18 からジェネリクスが取り入れられる関係で、any も標準の識別子として利用可能になります。 1095 | 1096 | ## 型の埋め込み 1097 | 1098 | Go では,継承はサポートされていません。 1099 | 1100 | 代わりにほかの型を「埋め込む」(Embed) という方式で構造体やインタフェースの振る舞いを拡張できます。 1101 | 1102 | 以下は Task に Reminder を埋め込むを埋め込み、Task が Reminder のプロパティやメソッドが利用可能になる例です。 1103 | 1104 | ```go 1105 | package main 1106 | 1107 | import ( 1108 | "fmt" 1109 | "time" 1110 | ) 1111 | 1112 | type Reminder struct { 1113 | ExpiredAt time.Time 1114 | } 1115 | 1116 | func (r *Reminder) RemainingTime() time.Duration { 1117 | now := time.Now() 1118 | return r.ExpiredAt.Sub(now) 1119 | } 1120 | 1121 | func NewReminder(minute int) *Reminder { 1122 | return &Reminder{ 1123 | ExpiredAt: time.Now().Add(time.Duration(minute) * time.Minute), 1124 | } 1125 | } 1126 | 1127 | type Task struct { 1128 | ID int 1129 | Detail string 1130 | Done bool 1131 | *Reminder // Reminderを埋め込む(Embed) 1132 | } 1133 | 1134 | func NewTask(id int, detail, firstName, lastName string) *Task { 1135 | task := &Task{ 1136 | ID: id, 1137 | Detail: detail, 1138 | Done: false, 1139 | Reminder: NewReminder(10), 1140 | } 1141 | return task 1142 | } 1143 | 1144 | func main() { 1145 | task := NewTask(1, "buy the milk", "Jxck", "Daniel") 1146 | 1147 | // TaskにReminderのプロパティやメソッドが埋め込まれている 1148 | fmt.Println(task.ExpiredAt) 1149 | fmt.Println(task.RemainingTime()) 1150 | } 1151 | ``` 1152 | 1153 | ## インタフェースの埋め込み 1154 | 1155 | 構造体( `struct` )だけではなくインターフェース( `interface` )も「埋め込む」(Embed) 事ができます 1156 | 1157 | ```go 1158 | // 読み込みメソッドがある事をインターフェースで宣言 1159 | type Reader interface { 1160 | Read(p []byte) (n int, err error) 1161 | } 1162 | 1163 | // 書き込みメソッドがある事をインターフェースで宣言 1164 | type Writer interface { 1165 | Write(p []byte) (n int, err error) 1166 | } 1167 | 1168 | // 読み込みメソッドも書き込みメソッドもある事をインターフェースで宣言 1169 | type ReadWriter interface { 1170 | Reader 1171 | Writer 1172 | } 1173 | ``` 1174 | 1175 | ## 型変換 (キャスト) 1176 | 1177 | 明示的に型変換(キャスト)ができます。 1178 | 1179 | ```go 1180 | var s string = "abc" 1181 | var b []byte = []byte(s) // string -> []byte 1182 | fmt.Println(b) // [97 98 99] 1183 | ``` 1184 | 1185 | キャストに失敗した場合はパニックが発生します。 1186 | 1187 | ```go 1188 | // cannot convert "a" (type string) to type int 1189 | a := int("a") 1190 | ``` 1191 | 1192 | ## 型の検査 1193 | 1194 | Type Assertion で型を調べる事ができます。 1195 | 1196 | 第一戻り値が元の値、第二戻り値が調べた結果です。 1197 | 1198 | ```go 1199 | s, ok := value.(string) // Type Assertion 1200 | if ok { 1201 | fmt.Printf("value is string: %s\n", s) 1202 | } else { 1203 | fmt.Printf("value is not string\n") 1204 | } 1205 | ``` 1206 | 1207 | Type Switch で型で分岐処理ができます。 1208 | 1209 | ```go 1210 | switch v := value.(type) { 1211 | case string: 1212 | fmt.Printf("value is string: %s\n", v) 1213 | case int: 1214 | fmt.Printf("value is int: %d\n", v) 1215 | case Stringer: 1216 | fmt.Printf("value is Stringer: %s\n", v) 1217 | } 1218 | ``` 1219 | 1220 | --- 1221 | 1222 | ## ゴルーチン 1223 | 1224 | 軽量スレッド、ゴルーチンを利用して非同期処理できる 1225 | 1226 | 以下はリクエストを 3 回送るコードです。 1227 | 1228 | 同期処理の場合は、1 度目のリクエスト完了後に 2 度目,3 度目...と直列で続きます。 1229 | 1230 | ```go 1231 | package main 1232 | 1233 | import ( 1234 | "fmt" 1235 | "log" 1236 | "net/http" 1237 | ) 1238 | 1239 | func main() { 1240 | urls := []string{ 1241 | "https://google.com", 1242 | "https://google.com", 1243 | "https://google.com", 1244 | } 1245 | for _, url := range urls { 1246 | res, err := http.Get(url) 1247 | if err != nil { 1248 | log.Fatal(err) 1249 | } 1250 | defer res.Body.Close() 1251 | fmt.Println(url, res.Status) 1252 | } 1253 | } 1254 | ``` 1255 | 1256 | `go` キーワードで非同期処理で実装した場合は、並行してリクエストを行うことができます。 1257 | 1258 | ```go 1259 | package main 1260 | 1261 | import ( 1262 | "fmt" 1263 | "log" 1264 | "net/http" 1265 | "sync" 1266 | ) 1267 | 1268 | func main() { 1269 | wait := new(sync.WaitGroup) 1270 | urls := []string{ 1271 | "https://google.com", 1272 | "https://google.com", 1273 | "https://google.com", 1274 | } 1275 | for _, url := range urls { 1276 | // waitGroupに追加 1277 | wait.Add(1) 1278 | // 取得処理をゴルーチンで実行する 1279 | go func(url string) { 1280 | res, err := http.Get(url) 1281 | if err != nil { 1282 | log.Fatal(err) 1283 | } 1284 | defer res.Body.Close() 1285 | fmt.Println(url, res.Status) 1286 | // waitGroupから削除 1287 | wait.Done() 1288 | }(url) 1289 | } 1290 | // 待ち合わせ 1291 | wait.Wait() 1292 | } 1293 | ``` 1294 | 1295 | ## チャネル 1296 | 1297 | 複数のゴルーチンのデータをやりとりしたい場合チャネルを利用することができます。。 1298 | 1299 | まずは組み込み関数 `make` を使い、以下のようにチャネルを作成、書き込み、読み込みができます。 1300 | 1301 | ```go 1302 | // stringを扱うチャネルを生成 1303 | ch := make(chan string) 1304 | 1305 | // チャネルにstringを書き込む 1306 | ch <- "a" 1307 | 1308 | // チャネルからstringを読み出す 1309 | message := <- ch 1310 | ``` 1311 | 1312 | チャネルを利用して並行して HTTP リクエストし、早く取得されたステータスから順に受け取って処理しておく事ができます。 1313 | 1314 | ```go 1315 | package main 1316 | 1317 | import ( 1318 | "fmt" 1319 | "log" 1320 | "net/http" 1321 | ) 1322 | 1323 | func main() { 1324 | urls := []string{ 1325 | "https://google.com", 1326 | "https://google.com", 1327 | "https://google.com", 1328 | } 1329 | statusChan := make(chan string) 1330 | for _, url := range urls { 1331 | // 取得処理をゴルーチンで実行する 1332 | go func(url string) { 1333 | res, err := http.Get(url) 1334 | if err != nil { 1335 | log.Fatal(err) 1336 | } 1337 | defer res.Body.Close() 1338 | statusChan <- res.Status 1339 | }(url) 1340 | } 1341 | for i := 0; i < len(urls); i++ { 1342 | // waitする必要がなく、先に受け取ったチャネルから処理できる 1343 | fmt.Println(<-statusChan) 1344 | } 1345 | } 1346 | ``` 1347 | 1348 | ## select 文を用いたイベント制御 1349 | 1350 | 読み出しや書き込みイベント制御のための select 文もあります。 1351 | 1352 | 主な用途は for/select 文と break を用いて実装するタイムアウト処理などに利用されます。 1353 | 1354 | ```go 1355 | ch1 := make(chan string) 1356 | ch2 := make(chan string) 1357 | for { 1358 | select { 1359 | case c1 := <-ch1: 1360 | // ch1からデータを読み出したときに実行される 1361 | case c2 := <-ch2: 1362 | // ch2からデータを読み出したときに実行される 1363 | case ch2 <- "c": 1364 | // ch2にデータを書き込んだときに実行される 1365 | } 1366 | } 1367 | ``` 1368 | 1369 | また、`make` 関数には同時に 3 つまで読み出されないかぎりチャネルに書き込まない制御をするバッファ付きチャネルや、 1370 | 1371 | 同時起動数制御などメッセージキューのような動作をしてくれるバッファ指定引数があります。 1372 | 1373 | --- 1374 | 1375 | ## サーバーを立ててみよう 1376 | 1377 | `httprouter` を使って簡単なサーバーを立ててみましょう。 1378 | 1379 | ```sh 1380 | $ go mod init main 1381 | $ go get github.com/julienschmidt/httprouter@latest 1382 | ``` 1383 | 1384 | `main.go` 1385 | 1386 | ```go 1387 | package main 1388 | 1389 | import ( 1390 | "fmt" 1391 | "log" 1392 | "net/http" 1393 | 1394 | "github.com/julienschmidt/httprouter" 1395 | ) 1396 | 1397 | func Hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 1398 | fmt.Fprintf(w, "Hello golang") 1399 | } 1400 | 1401 | func FizzBuzz(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 1402 | num := p.ByName("num") 1403 | 1404 | // TODO: numをint型に変換 1405 | // TODO: 3の倍数の時にFizz 1406 | // TODO: 5の倍数の時にBuzz 1407 | // TODO: 15の倍数の時にFizzBuzz 1408 | // TODO: それ以外の場合に数値をそのまま表示 1409 | 1410 | fmt.Fprint(w, num) 1411 | } 1412 | 1413 | func main() { 1414 | router := httprouter.New() 1415 | 1416 | router.GET("/", Hello) 1417 | router.GET("/fizzbuzz/:num", FizzBuzz) 1418 | 1419 | err := http.ListenAndServe(":8080", router) 1420 | if err != nil { 1421 | log.Fatal(err) 1422 | } 1423 | } 1424 | ``` 1425 | 1426 | API サーバーを実行します。 1427 | 1428 | ```sh 1429 | $ go run main.go 1430 | ``` 1431 | 1432 | コードを確認しながら試して見ましょう。 1433 | 1434 | ```sh 1435 | $ curl http://localhost:8080/ 1436 | Hello golang 1437 | ``` 1438 | 1439 | `/fizzbuzz/:num` で FizzBuzz API を作って見ましょう。 1440 | 1441 | - `strconv.Atoi` を使い、文字列を数値型にキャストできます。 https://xn--go-hh0g6u.com/pkg/strconv/ 1442 | - `fmt.Fprint` に `http.ResponseWriter` を渡すことで body に書き込みが行えます。 https://github.com/julienschmidt/httprouter 1443 | 1444 | ```sh 1445 | $ curl http://localhost:8080/fizzbuzz/1 1446 | 1 1447 | $ curl http://localhost:8080/fizzbuzz/2 1448 | 2 1449 | $ curl http://localhost:8080/fizzbuzz/3 1450 | Fizz! 1451 | $ curl http://localhost:8080/fizzbuzz/4 1452 | 4 1453 | $ curl http://localhost:8080/fizzbuzz/5 1454 | 5 1455 | $ curl http://localhost:8080/fizzbuzz/6 1456 | Buzz! 1457 | $ curl http://localhost:8080/fizzbuzz/7 1458 | 7 1459 | $ curl http://localhost:8080/fizzbuzz/15 1460 | FizzBuzz! 1461 | ``` 1462 | --------------------------------------------------------------------------------