diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a725465 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fcf536d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '2' +services: + db: + image: mysql:5.7 + environment: + MYSQL_DATABASE: sample + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + volumes: + - db-data:/var/lib/mysql + - ./mysql:/docker-entrypoint-initdb.d + ports: + - "3306:3306" + app: + image: golang:1.6 + environment: + MYAPP_DATABASE_HOST: db + command: "go run server.go" + volumes: + - ./src:/go/src + working_dir: /go/src/app + ports: + - "8080:8080" + links: + - db + +volumes: + db-data: diff --git a/mysql/setup.sql b/mysql/setup.sql new file mode 100644 index 0000000..d356216 --- /dev/null +++ b/mysql/setup.sql @@ -0,0 +1,12 @@ +CREATE TABLE users ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + first_name VARCHAR(20) NOT NULL, + last_name VARCHAR(20) NOT NULL, + PRIMARY KEY (id) +); + +insert into users(first_name, last_name) values("Patricia", "Smith"); +insert into users(first_name, last_name) values("Linda", "Johnson"); +insert into users(first_name, last_name) values("Mary", "William"); +insert into users(first_name, last_name) values("Robert", "Jones"); +insert into users(first_name, last_name) values("James", "Brown"); diff --git a/src/app/domain/user.go b/src/app/domain/user.go new file mode 100644 index 0000000..9097a0c --- /dev/null +++ b/src/app/domain/user.go @@ -0,0 +1,17 @@ +package domain + +import "fmt" + +type Users []User + +type User struct { + ID int + FirstName string + LastName string + FullName string +} + +func (u *User) Build() *User { + u.FullName = fmt.Sprintf("%s %s", u.FirstName, u.LastName) + return u +} diff --git a/src/app/glide.lock b/src/app/glide.lock new file mode 100644 index 0000000..5c4ca6b --- /dev/null +++ b/src/app/glide.lock @@ -0,0 +1,33 @@ +hash: dc798e47f40e287732eccc0b8fbdf9711de716a5e28fd0088b8b6628b90201a0 +updated: 2016-12-10T10:09:37.375457544+09:00 +imports: +- name: github.com/gin-gonic/gin + version: e2212d40c62a98b388a5eb48ecbdcf88534688ba + subpackages: + - binding + - render +- name: github.com/go-sql-driver/mysql + version: a0583e0143b1624142adab07e0e97fe106d99561 +- name: github.com/golang/protobuf + version: 2402d76f3d41f928c7902a765dfc872356dd3aad + subpackages: + - proto +- name: github.com/manucorporat/sse + version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d +- name: github.com/mattn/go-isatty + version: 30a891c33c7cde7b02a981314b4228ec99380cca +- name: golang.org/x/net + version: f315505cf3349909cdf013ea56690da34e96a451 + subpackages: + - context +- name: golang.org/x/sys + version: 478fcf54317e52ab69f40bb4c7a1520288d7f7ea + subpackages: + - unix +- name: gopkg.in/gin-gonic/gin.v1 + version: e2212d40c62a98b388a5eb48ecbdcf88534688ba +- name: gopkg.in/go-playground/validator.v8 + version: c193cecd124b5cc722d7ee5538e945bdb3348435 +- name: gopkg.in/yaml.v2 + version: a5b47d31c556af34a302ce5d659e6fea44d90de0 +testImports: [] diff --git a/src/app/glide.yaml b/src/app/glide.yaml new file mode 100644 index 0000000..bc36b82 --- /dev/null +++ b/src/app/glide.yaml @@ -0,0 +1,6 @@ +package: . +import: +- package: github.com/go-sql-driver/mysql + version: v1.3 +- package: gopkg.in/gin-gonic/gin.v1 + version: v1.1.4 diff --git a/src/app/infrastructure/router.go b/src/app/infrastructure/router.go new file mode 100644 index 0000000..35986a1 --- /dev/null +++ b/src/app/infrastructure/router.go @@ -0,0 +1,21 @@ +package infrastructure + +import ( + gin "gopkg.in/gin-gonic/gin.v1" + + "app/interfaces/controllers" +) + +var Router *gin.Engine + +func init() { + router := gin.Default() + + userController := controllers.NewUserController(NewSqlHandler()) + + router.POST("/users", func(c *gin.Context) { userController.Create(c) }) + router.GET("/users", func(c *gin.Context) { userController.Index(c) }) + router.GET("/users/:id", func(c *gin.Context) { userController.Show(c) }) + + Router = router +} diff --git a/src/app/infrastructure/sqlhandler.go b/src/app/infrastructure/sqlhandler.go new file mode 100644 index 0000000..4c825f7 --- /dev/null +++ b/src/app/infrastructure/sqlhandler.go @@ -0,0 +1,71 @@ +package infrastructure + +import ( + "database/sql" + + _ "github.com/go-sql-driver/mysql" + + "app/interfaces/database" +) + +type SqlHandler struct { + Conn *sql.DB +} + +func NewSqlHandler() database.SqlHandler { + conn, err := sql.Open("mysql", "root:@tcp(db:3306)/sample") + if err != nil { + panic(err.Error) + } + sqlHandler := new(SqlHandler) + sqlHandler.Conn = conn + return sqlHandler +} + +func (handler *SqlHandler) Execute(statement string, args ...interface{}) (database.Result, error) { + res := SqlResult{} + result, err := handler.Conn.Exec(statement, args...) + if err != nil { + return res, err + } + res.Result = result + return res, nil +} + +func (handler *SqlHandler) Query(statement string, args ...interface{}) (database.Row, error) { + rows, err := handler.Conn.Query(statement, args...) + if err != nil { + return new(SqlRow), err + } + row := new(SqlRow) + row.Rows = rows + return row, nil +} + +type SqlResult struct { + Result sql.Result +} + +func (r SqlResult) LastInsertId() (int64, error) { + return r.Result.LastInsertId() +} + +func (r SqlResult) RowsAffected() (int64, error) { + return r.Result.RowsAffected() +} + +type SqlRow struct { + Rows *sql.Rows +} + +func (r SqlRow) Scan(dest ...interface{}) error { + return r.Rows.Scan(dest...) +} + +func (r SqlRow) Next() bool { + return r.Rows.Next() +} + +func (r SqlRow) Close() error { + return r.Rows.Close() +} diff --git a/src/app/interfaces/controllers/context.go b/src/app/interfaces/controllers/context.go new file mode 100644 index 0000000..fea50ca --- /dev/null +++ b/src/app/interfaces/controllers/context.go @@ -0,0 +1,8 @@ +package controllers + +type Context interface { + Param(string) string + Bind(interface{}) error + Status(int) + JSON(int, interface{}) +} diff --git a/src/app/interfaces/controllers/error.go b/src/app/interfaces/controllers/error.go new file mode 100644 index 0000000..ab55009 --- /dev/null +++ b/src/app/interfaces/controllers/error.go @@ -0,0 +1,11 @@ +package controllers + +type Error struct { + Message string +} + +func NewError(err error) *Error { + return &Error{ + Message: err.Error(), + } +} diff --git a/src/app/interfaces/controllers/user_controller.go b/src/app/interfaces/controllers/user_controller.go new file mode 100644 index 0000000..2347620 --- /dev/null +++ b/src/app/interfaces/controllers/user_controller.go @@ -0,0 +1,52 @@ +package controllers + +import ( + "app/domain" + "app/interfaces/database" + "app/usecase" + "strconv" +) + +type UserController struct { + Interactor usecase.UserInteractor +} + +func NewUserController(sqlHandler database.SqlHandler) *UserController { + return &UserController{ + Interactor: usecase.UserInteractor{ + UserRepository: &database.UserRepository{ + SqlHandler: sqlHandler, + }, + }, + } +} + +func (controller *UserController) Create(c Context) { + u := domain.User{} + c.Bind(&u) + user, err := controller.Interactor.Add(u) + if err != nil { + c.JSON(500, NewError(err)) + return + } + c.JSON(201, user) +} + +func (controller *UserController) Index(c Context) { + users, err := controller.Interactor.Users() + if err != nil { + c.JSON(500, NewError(err)) + return + } + c.JSON(200, users) +} + +func (controller *UserController) Show(c Context) { + id, _ := strconv.Atoi(c.Param("id")) + user, err := controller.Interactor.UserById(id) + if err != nil { + c.JSON(500, NewError(err)) + return + } + c.JSON(200, user) +} diff --git a/src/app/interfaces/database/sqlhandler.go b/src/app/interfaces/database/sqlhandler.go new file mode 100644 index 0000000..d53d4db --- /dev/null +++ b/src/app/interfaces/database/sqlhandler.go @@ -0,0 +1,17 @@ +package database + +type SqlHandler interface { + Execute(string, ...interface{}) (Result, error) + Query(string, ...interface{}) (Row, error) +} + +type Result interface { + LastInsertId() (int64, error) + RowsAffected() (int64, error) +} + +type Row interface { + Scan(...interface{}) error + Next() bool + Close() error +} diff --git a/src/app/interfaces/database/user_repository.go b/src/app/interfaces/database/user_repository.go new file mode 100644 index 0000000..9fd4349 --- /dev/null +++ b/src/app/interfaces/database/user_repository.go @@ -0,0 +1,65 @@ +package database + +import "app/domain" + +type UserRepository struct { + SqlHandler +} + +func (repo *UserRepository) Store(u domain.User) (id int, err error) { + result, err := repo.Execute( + "INSERT INTO users (first_name, last_name) VALUES (?,?)", u.FirstName, u.LastName, + ) + if err != nil { + return + } + id64, err := result.LastInsertId() + if err != nil { + return + } + id = int(id64) + return +} + +func (repo *UserRepository) FindById(identifier int) (user domain.User, err error) { + row, err := repo.Query("SELECT id, first_name, last_name FROM users WHERE id = ?", identifier) + defer row.Close() + if err != nil { + return + } + var id int + var firstName string + var lastName string + row.Next() + if err = row.Scan(&id, &firstName, &lastName); err != nil { + return + } + user.ID = id + user.FirstName = firstName + user.LastName = lastName + user.Build() + return +} + +func (repo *UserRepository) FindAll() (users domain.Users, err error) { + rows, err := repo.Query("SELECT id, first_name, last_name FROM users") + defer rows.Close() + if err != nil { + return + } + for rows.Next() { + var id int + var firstName string + var lastName string + if err := rows.Scan(&id, &firstName, &lastName); err != nil { + continue + } + user := domain.User{ + ID: id, + FirstName: firstName, + LastName: lastName, + } + users = append(users, *user.Build()) + } + return +} diff --git a/src/app/server.go b/src/app/server.go new file mode 100644 index 0000000..eadfdca --- /dev/null +++ b/src/app/server.go @@ -0,0 +1,7 @@ +package main + +import "app/infrastructure" + +func main() { + infrastructure.Router.Run() +} diff --git a/src/app/usecase/user_interactor.go b/src/app/usecase/user_interactor.go new file mode 100644 index 0000000..803a099 --- /dev/null +++ b/src/app/usecase/user_interactor.go @@ -0,0 +1,26 @@ +package usecase + +import "app/domain" + +type UserInteractor struct { + UserRepository UserRepository +} + +func (interactor *UserInteractor) Add(u domain.User) (user domain.User, err error) { + identifier, err := interactor.UserRepository.Store(u) + if err != nil { + return + } + user, err = interactor.UserRepository.FindById(identifier) + return +} + +func (interactor *UserInteractor) Users() (user domain.Users, err error) { + user, err = interactor.UserRepository.FindAll() + return +} + +func (interactor *UserInteractor) UserById(identifier int) (user domain.User, err error) { + user, err = interactor.UserRepository.FindById(identifier) + return +} diff --git a/src/app/usecase/user_repository.go b/src/app/usecase/user_repository.go new file mode 100644 index 0000000..aecf8ff --- /dev/null +++ b/src/app/usecase/user_repository.go @@ -0,0 +1,9 @@ +package usecase + +import "app/domain" + +type UserRepository interface { + Store(domain.User) (int, error) + FindById(int) (domain.User, error) + FindAll() (domain.Users, error) +}