├── .gitignore ├── README.md ├── activate ├── main.go ├── setup.sql └── src ├── domain └── domain.go ├── infrastructure ├── logger.go ├── sqlitehandler.go └── sqlitehandler_test.go ├── interfaces ├── repositories.go ├── repositories_test.go └── webservice.go └── usecases └── usecases.go /.gitignore: -------------------------------------------------------------------------------- 1 | src/github.com/ 2 | *.swp 3 | pkg/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-cleanarchitecture 2 | ==================== 3 | 4 | An example Go application demonstrating The Clean Architecture. 5 | 6 | [http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/](http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/) 7 | 8 | 9 | Install 10 | ------- 11 | 12 | Create a new directory, where to host this project 13 | 14 | mkdir -p $GOPATH:src/github.com/manuelkiessling/ 15 | 16 | Check out the source 17 | 18 | cd $GOPATH:src/github.com/manuelkiessling/ 19 | git clone https://github.com/manuelkiessling/go-cleanarchitecture 20 | 21 | Setup the GOPATH to include this path 22 | 23 | cd go-cleanarchitecture 24 | export GOPATH=$GOPATH:`pwd` 25 | 26 | Then build the project 27 | 28 | go get 29 | go build 30 | 31 | Create the SQLite structure 32 | 33 | sqlite3 /var/tmp/production.sqlite < setup.sql 34 | 35 | Run the server 36 | 37 | go-cleanarchitecture 38 | 39 | Access the web endpoint at [http://localhost:8080/orders?userId=40&orderId=60](http://localhost:8080/orders?userId=40&orderId=60) 40 | 41 | To run the tests, for each module, run 42 | 43 | cd src/infrastructure && go test 44 | cd src/interfaces && go test 45 | 46 | Enjoy. 47 | 48 | 49 | License 50 | ------- 51 | The MIT License (MIT) 52 | 53 | Copyright (c) 2012 Manuel Kiessling 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 60 | -------------------------------------------------------------------------------- /activate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 3 | export GOPATH=$DIR 4 | 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "usecases" 5 | "interfaces" 6 | "infrastructure" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | dbHandler := infrastructure.NewSqliteHandler("/var/tmp/production.sqlite") 12 | 13 | handlers := make(map[string] interfaces.DbHandler) 14 | handlers["DbUserRepo"] = dbHandler 15 | handlers["DbCustomerRepo"] = dbHandler 16 | handlers["DbItemRepo"] = dbHandler 17 | handlers["DbOrderRepo"] = dbHandler 18 | 19 | orderInteractor := new(usecases.OrderInteractor) 20 | orderInteractor.UserRepository = interfaces.NewDbUserRepo(handlers) 21 | orderInteractor.ItemRepository = interfaces.NewDbItemRepo(handlers) 22 | orderInteractor.OrderRepository = interfaces.NewDbOrderRepo(handlers) 23 | orderInteractor.Logger = new(infrastructure.Logger) 24 | 25 | webserviceHandler := interfaces.WebserviceHandler{} 26 | webserviceHandler.OrderInteractor = orderInteractor 27 | 28 | http.HandleFunc("/orders", func(res http.ResponseWriter, req *http.Request) { 29 | webserviceHandler.ShowOrder(res, req) 30 | }) 31 | http.ListenAndServe(":8080", nil) 32 | } 33 | -------------------------------------------------------------------------------- /setup.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users (id INTEGER, customer_id INTEGER, is_admin VARCHAR(3)); 2 | CREATE TABLE customers (id INTEGER, name VARCHAR(42)); 3 | CREATE TABLE orders (id INTEGER, customer_id INTEGER); 4 | CREATE TABLE items (id INTEGER, name VARCHAR(42), value FLOAT, available VARCHAR(3)); 5 | CREATE TABLE items2orders (item_id INTEGER, order_id INTEGER); 6 | 7 | INSERT INTO users (id, customer_id, is_admin) VALUES (40, 50, "yes"); 8 | INSERT INTO customers (id, name) VALUES (50, "John Doe"); 9 | INSERT INTO orders (id, customer_id) VALUES (60, 50); 10 | INSERT INTO items (id, name, value, available) VALUES (101, "Soap", 4.99, "yes"); 11 | INSERT INTO items (id, name, value, available) VALUES (102, "Fork", 2.99, "yes"); 12 | INSERT INTO items (id, name, value, available) VALUES (103, "Bottle", 6.99, "no"); 13 | INSERT INTO items (id, name, value, available) VALUES (104, "Chair", 43.00, "yes"); 14 | 15 | INSERT INTO items2orders (item_id, order_id) VALUES (101, 60); 16 | INSERT INTO items2orders (item_id, order_id) VALUES (104, 60); 17 | 18 | # http://localhost:8080/orders?userId=40&orderId=60 19 | 20 | -------------------------------------------------------------------------------- /src/domain/domain.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type CustomerRepository interface { 8 | Store(customer Customer) 9 | FindById(id int) Customer 10 | } 11 | 12 | type ItemRepository interface { 13 | Store(item Item) 14 | FindById(id int) Item 15 | } 16 | 17 | type OrderRepository interface { 18 | Store(order Order) 19 | FindById(id int) Order 20 | } 21 | 22 | type Customer struct { 23 | Id int 24 | Name string 25 | } 26 | 27 | type Item struct { 28 | Id int 29 | Name string 30 | Value float64 31 | Available bool 32 | } 33 | 34 | type Order struct { 35 | Id int 36 | Customer Customer 37 | Items []Item 38 | } 39 | 40 | func (order *Order) Add(item Item) error { 41 | if !item.Available { 42 | return errors.New("Cannot add unavailable items to order") 43 | } 44 | if order.value()+item.Value > 250.00 { 45 | return errors.New(`An order may not exceed 46 | a total value of $250.00`) 47 | } 48 | order.Items = append(order.Items, item) 49 | return nil 50 | } 51 | 52 | func (order *Order) value() (sum float64) { 53 | for i := range order.Items { 54 | sum += order.Items[i].Value 55 | } 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /src/infrastructure/logger.go: -------------------------------------------------------------------------------- 1 | package infrastructure 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | type Logger struct{} 8 | 9 | func (logger Logger) Log(args ...interface{}) { 10 | log.Println(args...) 11 | } 12 | -------------------------------------------------------------------------------- /src/infrastructure/sqlitehandler.go: -------------------------------------------------------------------------------- 1 | package infrastructure 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | _ "github.com/mattn/go-sqlite3" 7 | "interfaces" 8 | ) 9 | 10 | type SqliteHandler struct { 11 | Conn *sql.DB 12 | } 13 | 14 | func (handler *SqliteHandler) Execute(statement string) { 15 | handler.Conn.Exec(statement) 16 | } 17 | 18 | func (handler *SqliteHandler) Query(statement string) interfaces.Row { 19 | //fmt.Println(statement) 20 | rows, err := handler.Conn.Query(statement) 21 | if err != nil { 22 | fmt.Println(err) 23 | return new(SqliteRow) 24 | } 25 | row := new(SqliteRow) 26 | row.Rows = rows 27 | return row 28 | } 29 | 30 | type SqliteRow struct { 31 | Rows *sql.Rows 32 | } 33 | 34 | func (r SqliteRow) Scan(dest ...interface{}) { 35 | r.Rows.Scan(dest...) 36 | } 37 | 38 | func (r SqliteRow) Next() bool { 39 | return r.Rows.Next() 40 | } 41 | 42 | func NewSqliteHandler(dbfileName string) *SqliteHandler { 43 | conn, _ := sql.Open("sqlite3", dbfileName) 44 | sqliteHandler := new(SqliteHandler) 45 | sqliteHandler.Conn = conn 46 | return sqliteHandler 47 | } 48 | -------------------------------------------------------------------------------- /src/infrastructure/sqlitehandler_test.go: -------------------------------------------------------------------------------- 1 | package infrastructure 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_SqliteHandler(t *testing.T) { 9 | h := NewSqliteHandler("/var/tmp/sqlitehandler_test.db") 10 | h.Execute("DROP TABLE foo") 11 | h.Execute("CREATE TABLE foo (id integer, name varchar(42))") 12 | h.Execute("INSERT INTO foo (id, name) VALUES (23, 'johndoe')") 13 | row := h.Query("SELECT id, name FROM foo LIMIT 1") 14 | var id int 15 | var name string 16 | row.Next() 17 | row.Scan(&id, &name) 18 | if id != 23 { 19 | fmt.Println(id) 20 | t.Error() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/interfaces/repositories.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "domain" 5 | "fmt" 6 | "usecases" 7 | ) 8 | 9 | type DbHandler interface { 10 | Execute(statement string) 11 | Query(statement string) Row 12 | } 13 | 14 | type Row interface { 15 | Scan(dest ...interface{}) 16 | Next() bool 17 | } 18 | 19 | type DbRepo struct { 20 | dbHandlers map[string]DbHandler 21 | dbHandler DbHandler 22 | } 23 | 24 | type DbUserRepo DbRepo 25 | type DbCustomerRepo DbRepo 26 | type DbOrderRepo DbRepo 27 | type DbItemRepo DbRepo 28 | 29 | func NewDbUserRepo(dbHandlers map[string]DbHandler) *DbUserRepo { 30 | dbUserRepo := new(DbUserRepo) 31 | dbUserRepo.dbHandlers = dbHandlers 32 | dbUserRepo.dbHandler = dbHandlers["DbUserRepo"] 33 | return dbUserRepo 34 | } 35 | 36 | func (repo *DbUserRepo) Store(user usecases.User) { 37 | isAdmin := "no" 38 | if user.IsAdmin { 39 | isAdmin = "yes" 40 | } 41 | repo.dbHandler.Execute(fmt.Sprintf("INSERT INTO users (id, customer_id, is_admin) VALUES ('%d', '%d', '%v')", user.Id, user.Customer.Id, isAdmin)) 42 | customerRepo := NewDbCustomerRepo(repo.dbHandlers) 43 | customerRepo.Store(user.Customer) 44 | } 45 | 46 | func (repo *DbUserRepo) FindById(id int) usecases.User { 47 | row := repo.dbHandler.Query(fmt.Sprintf("SELECT is_admin, customer_id FROM users WHERE id = '%d' LIMIT 1", id)) 48 | var isAdmin string 49 | var customerId int 50 | row.Next() 51 | row.Scan(&isAdmin, &customerId) 52 | customerRepo := NewDbCustomerRepo(repo.dbHandlers) 53 | u := usecases.User{Id: id, Customer: customerRepo.FindById(customerId)} 54 | u.IsAdmin = false 55 | if isAdmin == "yes" { 56 | u.IsAdmin = true 57 | } 58 | return u 59 | } 60 | 61 | func NewDbCustomerRepo(dbHandlers map[string]DbHandler) *DbCustomerRepo { 62 | dbCustomerRepo := new(DbCustomerRepo) 63 | dbCustomerRepo.dbHandlers = dbHandlers 64 | dbCustomerRepo.dbHandler = dbHandlers["DbCustomerRepo"] 65 | return dbCustomerRepo 66 | } 67 | 68 | func (repo *DbCustomerRepo) Store(customer domain.Customer) { 69 | repo.dbHandler.Execute(fmt.Sprintf("INSERT INTO customers (id, name) VALUES ('%d', '%v')", customer.Id, customer.Name)) 70 | } 71 | 72 | func (repo *DbCustomerRepo) FindById(id int) domain.Customer { 73 | row := repo.dbHandler.Query(fmt.Sprintf("SELECT name FROM customers WHERE id = '%d' LIMIT 1", id)) 74 | var name string 75 | row.Next() 76 | row.Scan(&name) 77 | return domain.Customer{Id: id, Name: name} 78 | } 79 | 80 | func NewDbOrderRepo(dbHandlers map[string]DbHandler) *DbOrderRepo { 81 | dbOrderRepo := new(DbOrderRepo) 82 | dbOrderRepo.dbHandlers = dbHandlers 83 | dbOrderRepo.dbHandler = dbHandlers["DbOrderRepo"] 84 | return dbOrderRepo 85 | } 86 | 87 | func (repo *DbOrderRepo) Store(order domain.Order) { 88 | repo.dbHandler.Execute(fmt.Sprintf("INSERT INTO orders (id, customer_id) VALUES ('%d', '%v')", order.Id, order.Customer.Id)) 89 | for _, item := range order.Items { 90 | repo.dbHandler.Execute(fmt.Sprintf("INSERT INTO items2orders (item_id, order_id) VALUES ('%d', '%d')", item.Id, order.Id)) 91 | } 92 | } 93 | 94 | func (repo *DbOrderRepo) FindById(id int) domain.Order { 95 | row := repo.dbHandler.Query(fmt.Sprintf("SELECT customer_id FROM orders WHERE id = '%d' LIMIT 1", id)) 96 | var customerId int 97 | row.Next() 98 | row.Scan(&customerId) 99 | customerRepo := NewDbCustomerRepo(repo.dbHandlers) 100 | order := domain.Order{Id: id, Customer: customerRepo.FindById(customerId)} 101 | var itemId int 102 | itemRepo := NewDbItemRepo(repo.dbHandlers) 103 | row = repo.dbHandler.Query(fmt.Sprintf("SELECT item_id FROM items2orders WHERE order_id = '%d'", order.Id)) 104 | for row.Next() { 105 | row.Scan(&itemId) 106 | order.Add(itemRepo.FindById(itemId)) 107 | } 108 | return order 109 | } 110 | 111 | func NewDbItemRepo(dbHandlers map[string]DbHandler) *DbItemRepo { 112 | dbItemRepo := new(DbItemRepo) 113 | dbItemRepo.dbHandlers = dbHandlers 114 | dbItemRepo.dbHandler = dbHandlers["DbItemRepo"] 115 | return dbItemRepo 116 | } 117 | 118 | func (repo *DbItemRepo) Store(item domain.Item) { 119 | available := "no" 120 | if item.Available { 121 | available = "yes" 122 | } 123 | repo.dbHandler.Execute(fmt.Sprintf("INSERT INTO items (id, name, value, available) VALUES ('%d', '%v', '%f', '%v')", item.Id, item.Name, item.Value, available)) 124 | } 125 | 126 | func (repo *DbItemRepo) FindById(id int) domain.Item { 127 | row := repo.dbHandler.Query(fmt.Sprintf("SELECT name, value, available FROM items WHERE id = '%d' LIMIT 1", id)) 128 | var name string 129 | var value float64 130 | var available string 131 | row.Next() 132 | row.Scan(&name, &value, &available) 133 | item := domain.Item{Id: id, Name: name, Value: value} 134 | item.Available = false 135 | if available == "yes" { 136 | item.Available = true 137 | } 138 | return item 139 | } 140 | -------------------------------------------------------------------------------- /src/interfaces/repositories_test.go: -------------------------------------------------------------------------------- 1 | package interfaces_test 2 | 3 | import ( 4 | "domain" 5 | _ "fmt" 6 | "infrastructure" 7 | "interfaces" 8 | "testing" 9 | "usecases" 10 | ) 11 | 12 | func Test_UserRepository(t *testing.T) { 13 | dbHandler := infrastructure.NewSqliteHandler("/var/tmp/test1.sqlite") 14 | dbHandler.Execute("DROP TABLE users") 15 | dbHandler.Execute("DROP TABLE customers") 16 | dbHandler.Execute("CREATE TABLE users (id INTEGER, customer_id INTEGER, is_admin VARCHAR(3))") 17 | dbHandler.Execute("CREATE TABLE customers (id INTEGER, name VARCHAR(42))") 18 | 19 | handlers := make(map[string]interfaces.DbHandler) 20 | handlers["DbUserRepo"] = dbHandler 21 | handlers["DbCustomerRepo"] = dbHandler 22 | 23 | ur := interfaces.NewDbUserRepo(handlers) 24 | 25 | c := domain.Customer{} 26 | c.Id = 555 27 | c.Name = "John Doe" 28 | u := usecases.User{} 29 | u.Id = 6 30 | u.IsAdmin = true 31 | u.Customer = c 32 | ur.Store(u) 33 | 34 | u = ur.FindById(6) 35 | if u.Id != 6 { 36 | t.Error() 37 | } 38 | if u.IsAdmin != true { 39 | t.Error() 40 | } 41 | if u.Customer.Id != 555 { 42 | t.Error() 43 | } 44 | if u.Customer.Name != "John Doe" { 45 | t.Error() 46 | } 47 | } 48 | 49 | func Test_OrderRepository(t *testing.T) { 50 | dbHandler := infrastructure.NewSqliteHandler("/var/tmp/test2.sqlite") 51 | dbHandler.Execute("DROP TABLE users") 52 | dbHandler.Execute("DROP TABLE customers") 53 | dbHandler.Execute("DROP TABLE orders") 54 | dbHandler.Execute("DROP TABLE items") 55 | dbHandler.Execute("DROP TABLE items2orders") 56 | dbHandler.Execute("CREATE TABLE users (id INTEGER, customer_id INTEGER, is_admin VARCHAR(3))") 57 | dbHandler.Execute("CREATE TABLE customers (id INTEGER, name VARCHAR(42))") 58 | dbHandler.Execute("CREATE TABLE orders (id INTEGER, customer_id INTEGER)") 59 | dbHandler.Execute("CREATE TABLE items (id INTEGER, name VARCHAR(42), value FLOAT, available VARCHAR(3))") 60 | dbHandler.Execute("CREATE TABLE items2orders (item_id INTEGER, order_id INTEGER)") 61 | 62 | handlers := make(map[string]interfaces.DbHandler) 63 | handlers["DbUserRepo"] = dbHandler 64 | handlers["DbCustomerRepo"] = dbHandler 65 | handlers["DbOrderRepo"] = dbHandler 66 | handlers["DbItemRepo"] = dbHandler 67 | 68 | ur := interfaces.NewDbUserRepo(handlers) 69 | or := interfaces.NewDbOrderRepo(handlers) 70 | ir := interfaces.NewDbItemRepo(handlers) 71 | 72 | c := domain.Customer{} 73 | c.Id = 555 74 | c.Name = "John Doe" 75 | u := usecases.User{} 76 | u.Id = 6 77 | u.IsAdmin = true 78 | u.Customer = c 79 | ur.Store(u) 80 | 81 | i1 := domain.Item{} 82 | i1.Id = 101 83 | i1.Name = "Car" 84 | i1.Value = 125.45 85 | i1.Available = true 86 | ir.Store(i1) 87 | 88 | i2 := domain.Item{} 89 | i2.Id = 102 90 | i2.Name = "Table" 91 | i2.Value = 432.56 92 | i2.Available = false 93 | ir.Store(i2) 94 | 95 | i3 := domain.Item{} 96 | i3.Id = 103 97 | i3.Name = "Chair" 98 | i3.Value = 52.31 99 | i3.Available = true 100 | ir.Store(i3) 101 | 102 | o := domain.Order{} 103 | o.Id = 39 104 | o.Customer = c 105 | o.Add(i1) 106 | o.Add(i2) //fails because it's not available 107 | o.Add(i3) 108 | or.Store(o) 109 | 110 | o = or.FindById(39) 111 | 112 | if o.Customer.Name != "John Doe" { 113 | t.Error() 114 | } 115 | 116 | items := o.Items 117 | if items[1].Id != 103 { 118 | t.Error() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/interfaces/webservice.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "usecases" 9 | ) 10 | 11 | type OrderInteractor interface { 12 | Items(userId, orderId int) ([]usecases.Item, error) 13 | Add(userId, orderId, itemId int) error 14 | } 15 | 16 | type WebserviceHandler struct { 17 | OrderInteractor OrderInteractor 18 | } 19 | 20 | func (handler WebserviceHandler) ShowOrder(res http.ResponseWriter, req *http.Request) { 21 | userId, _ := strconv.Atoi(req.FormValue("userId")) 22 | orderId, _ := strconv.Atoi(req.FormValue("orderId")) 23 | items, _ := handler.OrderInteractor.Items(userId, orderId) 24 | for _, item := range items { 25 | io.WriteString(res, fmt.Sprintf("item id: %d\n", item.Id)) 26 | io.WriteString(res, fmt.Sprintf("item name: %v\n", item.Name)) 27 | io.WriteString(res, fmt.Sprintf("item value: %f\n", item.Value)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/usecases/usecases.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | "domain" 5 | "fmt" 6 | ) 7 | 8 | type UserRepository interface { 9 | Store(user User) 10 | FindById(id int) User 11 | } 12 | 13 | type User struct { 14 | Id int 15 | IsAdmin bool 16 | Customer domain.Customer 17 | } 18 | 19 | type Item struct { 20 | Id int 21 | Name string 22 | Value float64 23 | } 24 | 25 | type Logger interface { 26 | Log(args ...interface{}) 27 | } 28 | 29 | type OrderInteractor struct { 30 | UserRepository UserRepository 31 | OrderRepository domain.OrderRepository 32 | ItemRepository domain.ItemRepository 33 | Logger Logger 34 | } 35 | 36 | func (interactor *OrderInteractor) Items(userId, orderId int) ([]Item, error) { 37 | var items []Item 38 | user := interactor.UserRepository.FindById(userId) 39 | order := interactor.OrderRepository.FindById(orderId) 40 | if user.Customer.Id != order.Customer.Id { 41 | message := "User #%d (customer #%d) " 42 | message += "is not allowed to see items " 43 | message += "in order #%d (of customer #%d)" 44 | err := fmt.Errorf(message, 45 | user.Id, 46 | user.Customer.Id, 47 | order.Id, 48 | order.Customer.Id) 49 | interactor.Logger.Log(err.Error()) 50 | items = make([]Item, 0) 51 | return items, err 52 | } 53 | items = make([]Item, len(order.Items)) 54 | for i, item := range order.Items { 55 | items[i] = Item{item.Id, item.Name, item.Value} 56 | } 57 | return items, nil 58 | } 59 | 60 | func (interactor *OrderInteractor) Add(userId, orderId, itemId int) error { 61 | var message string 62 | user := interactor.UserRepository.FindById(userId) 63 | order := interactor.OrderRepository.FindById(orderId) 64 | if user.Customer.Id != order.Customer.Id { 65 | message = "User #%d (customer #%d) " 66 | message += "is not allowed to add items " 67 | message += "to order #%d (of customer #%d)" 68 | err := fmt.Errorf(message, 69 | user.Id, 70 | user.Customer.Id, 71 | order.Id, 72 | order.Customer.Id) 73 | interactor.Logger.Log(err.Error()) 74 | return err 75 | } 76 | item := interactor.ItemRepository.FindById(itemId) 77 | if domainErr := order.Add(item); domainErr != nil { 78 | message = "Could not add item #%d " 79 | message += "to order #%d (of customer #%d) " 80 | message += "as user #%d because a business " 81 | message += "rule was violated: '%s'" 82 | err := fmt.Errorf(message, 83 | item.Id, 84 | order.Id, 85 | order.Customer.Id, 86 | user.Id, 87 | domainErr.Error()) 88 | interactor.Logger.Log(err.Error()) 89 | return err 90 | } 91 | interactor.OrderRepository.Store(order) 92 | interactor.Logger.Log(fmt.Sprintf( 93 | "User added item '%s' (#%d) to order #%d", 94 | item.Name, item.Id, order.Id)) 95 | return nil 96 | } 97 | 98 | type AdminOrderInteractor struct { 99 | OrderInteractor 100 | } 101 | 102 | func (interactor *AdminOrderInteractor) Add(userId, orderId, itemId int) error { 103 | var message string 104 | user := interactor.UserRepository.FindById(userId) 105 | order := interactor.OrderRepository.FindById(orderId) 106 | if !user.IsAdmin { 107 | message = "User #%d (customer #%d) " 108 | message += "is not allowed to add items " 109 | message += "to order #%d (of customer #%d), " 110 | message += "because he is not an administrator" 111 | err := fmt.Errorf(message, 112 | user.Id, 113 | user.Customer.Id, 114 | order.Id, 115 | order.Customer.Id) 116 | interactor.Logger.Log(err.Error()) 117 | return err 118 | } 119 | item := interactor.ItemRepository.FindById(itemId) 120 | if domainErr := order.Add(item); domainErr != nil { 121 | message = "Could not add item #%d " 122 | message += "to order #%d (of customer #%d) " 123 | message += "as user #%d because a business " 124 | message += "rule was violated: '%s'" 125 | err := fmt.Errorf(message, 126 | item.Id, 127 | order.Id, 128 | order.Customer.Id, 129 | user.Id, 130 | domainErr.Error()) 131 | interactor.Logger.Log(err.Error()) 132 | return err 133 | } 134 | interactor.OrderRepository.Store(order) 135 | interactor.Logger.Log(fmt.Sprintf( 136 | "Admin added item '%s' (#%d) to order #%d", 137 | item.Name, item.Id, order.Id)) 138 | return nil 139 | } 140 | --------------------------------------------------------------------------------