Skip to content

Commit

Permalink
Merge pull request #8 from bmf-san/feature/v3-0-0
Browse files Browse the repository at this point in the history
V3.0.0
  • Loading branch information
bmf-san authored Jul 7, 2019
2 parents c778a04 + 4871ff9 commit b4d3fbd
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 76 deletions.
66 changes: 40 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# go-api-boilerplate
A web application boilerplate built with go and clean architecture.
Most of this application built by standard libraly.

# Get Started
`cp app/.env_example app/.env`
Expand All @@ -10,7 +11,7 @@ After running docker, you need to execute sql files in `app/database/sql`.

# Architecture
```
app/
app
├── database
│   ├── migrations
│   │   └── schema.sql
Expand All @@ -19,6 +20,7 @@ app/
├── domain
│   ├── post.go
│   └── user.go
├── go_clean_architecture_web_application_boilerplate
├── infrastructure
│   ├── env.go
│   ├── logger.go
Expand All @@ -34,47 +36,59 @@ app/
│   ├── access.log
│   └── error.log
├── main.go
├── test.log
└── usecases
├── logger.go
├── post_interactor.go
├── post_repository.go
├── user_interactor.go
└── user_repository.go
8 directories, 22 files
```

| Layer | Directory | Sub directory |
|----------------------|----------------|--------------------------------------|
| Frameworks & Drivers | infrastructure | database, router, env |
| Interface | interfaces | controllers, database, repositories |
| Usecases | usecases | repositories |
| Entities | domain | ex. user.go |
| Layer | Directory |
|----------------------|----------------|
| Frameworks & Drivers | infrastructure |
| Interface | interfaces |
| Usecases | usecases |
| Entities | domain |

# API Document
// TODO: This section will be updated the date when ver.3.0.0 will be released.
# API

| ENDPOINT | HTTP Method | Parameters |
|----------|----------------|---------------|
| /users | GET | |
| /user | GET | ?id=[int] |
| /posts | GET | |
| /post | POST | |
| /post | DELETE | ?id=[int] |

# Controller method naming rule
Use these word as prefix.

- index
- Display a listing of the resource.
- show
- Display the specified resource.
- store
- Store a newly created resource in storage.
- update
- Update the specified resource in storage.
- destory
- Remove the specified resource from storage.
| Controller Method | HTTP Method | Description |
|-------------------|-------------|--------------------------------------------|
| Index | GET | Display a listing of the resource |
| Store | POST | Store a newly created resource in storage |
| Show | GET | Display the specified resource |
| Update | PUT/PATCH | Update the specified resource in storage |
| Destroy | DELETE | Remove the specified resource from storage |

# Repository method naming rule
Use these word as prefix.

- get
- add
- update
- delete
| Repository Method | Description |
|-------------------|------------------------------------------------------|
| FindByXX | Returns the entity identified by the given XX |
| FindAll | Returns all entities |
| Save | Saves the given entity |
| SaveByXX | Saves the given entity identified by the given XX |
| DeleteByXX | Deletes the entity identified by the given XX |
| Count | Returns the number of entities |
| ExistsBy | Indicates whether an entity with the given ID exists |

cf. [Spring Data JPA - Reference Documentation](https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.core-concepts)

# Tests
I have no tests because of my laziness, but I will prepare tests in [github - Gobel](https://github.com/bmf-san/Gobel) which is a my more practical clean architecture application with golang.

# References
- [github - manuelkiessling/go-cleanarchitecture](https://github.com/manuelkiessling/go-cleanarchitecture)
Expand Down
15 changes: 9 additions & 6 deletions app/infrastructure/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ import (

"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/interfaces"
"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/usecases"
"github.com/go-chi/chi"
)

// Dispatch is handle routing
func Dispatch(logger usecases.Logger, sqlHandler interfaces.SQLHandler, mux *http.ServeMux) {
func Dispatch(logger usecases.Logger, sqlHandler interfaces.SQLHandler) {
userController := interfaces.NewUserController(sqlHandler, logger)
postController := interfaces.NewPostController(sqlHandler, logger)

// TODO: Maybe I'll implement a routing package ...
mux.HandleFunc("/users", userController.Index)
mux.HandleFunc("/user", userController.Show)
mux.HandleFunc("/post", postController.Store)
r := chi.NewRouter()
r.Get("/users", userController.Index)
r.Get("/user", userController.Show)
r.Get("/posts", postController.Index)
r.Post("/post", postController.Store)
r.Delete("/post", postController.Destroy)

if err := http.ListenAndServe(":"+os.Getenv("SERVER_PORT"), mux); err != nil {
if err := http.ListenAndServe(":"+os.Getenv("SERVER_PORT"), r); err != nil {
logger.LogError("%s", err)
}
}
63 changes: 56 additions & 7 deletions app/interfaces/post_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package interfaces
import (
"encoding/json"
"net/http"
"strconv"

"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/domain"
"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/usecases"
)

Expand All @@ -25,15 +27,62 @@ func NewPostController(sqlHandler SQLHandler, logger usecases.Logger) *PostContr
}
}

// Store a newly created resource of a Post.
func (uc *PostController) Store(w http.ResponseWriter, r *http.Request) {
uc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
// Index is display a listing of the resource.
func (pc *PostController) Index(w http.ResponseWriter, r *http.Request) {
pc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)

Post, err := uc.PostInteractor.StorePost()
posts, err := pc.PostInteractor.Index()
if err != nil {
uc.Logger.LogError("%s", err)
// TODO: To return a error response message
pc.Logger.LogError("%s", err)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
json.NewEncoder(w).Encode(err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(posts)
}

// Store is stora a newly created resource in storage.
func (pc *PostController) Store(w http.ResponseWriter, r *http.Request) {
pc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)

p := domain.Post{}
err := json.NewDecoder(r.Body).Decode(&p)
if err != nil {
pc.Logger.LogError("%s", err)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
json.NewEncoder(w).Encode(err)
}

post, err := pc.PostInteractor.Store(p)
if err != nil {
pc.Logger.LogError("%s", err)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
json.NewEncoder(w).Encode(err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(post)
}

// Destroy is remove the specified resource from storage.
func (pc *PostController) Destroy(w http.ResponseWriter, r *http.Request) {
pc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)

postID, _ := strconv.Atoi(r.URL.Query().Get("id"))

err := pc.PostInteractor.Destroy(postID)
if err != nil {
pc.Logger.LogError("%s", err)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
json.NewEncoder(w).Encode(err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Post)
json.NewEncoder(w).Encode(nil)
}
78 changes: 65 additions & 13 deletions app/interfaces/post_repository.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,69 @@
package interfaces

import "github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/domain"

// A PostRepository belong to the inteface layer
type PostRepository struct {
SQLHandler SQLHandler
}

// AddPost add a newly created resource of a Post.
func (ur *PostRepository) AddPost() (id int64, err error) {
// NOTE: this is a transaction example.
tx, err := ur.SQLHandler.Begin()
// FindAll is returns all entities.
func (pr *PostRepository) FindAll() (posts domain.Posts, err error) {
const query = `
SELECT
id,
user_id,
body
FROM
posts
`

rows, err := pr.SQLHandler.Query(query)

defer rows.Close()

if err != nil {
return
}

const query = `
insert into
posts(id, user_id, body)
values
(?, ?, ?)
`
for rows.Next() {
var id int
var userID int
var body string
if err = rows.Scan(&id, &userID, &body); err != nil {
return
}
post := domain.Post{
ID: id,
UserID: userID,
Body: body,
}
posts = append(posts, post)
}

_, err = tx.Exec(query, 1, 1, "hoge")
if err != nil {
if err = rows.Err(); err != nil {
return
}

result, err := tx.Exec(query, 100, 1, "Hello, World. This is newly inserted.")
return
}

// Save is saves the given entity.
func (pr *PostRepository) Save(p domain.Post) (id int64, err error) {
// NOTE: this is a transaction example.
tx, err := pr.SQLHandler.Begin()
if err != nil {
return
}

const query = `
INSERT INTO
posts(user_id, body)
VALUES
(?, ?)
`

result, err := tx.Exec(query, p.UserID, p.Body)
if err != nil {
_ = tx.Rollback()
return
Expand All @@ -46,3 +80,21 @@ func (ur *PostRepository) AddPost() (id int64, err error) {

return
}

// DeleteByID is deletes the entity identified by the given id.
func (pr *PostRepository) DeleteByID(postID int) (err error) {
const query = `
DELETE
FROM
posts
WHERE
id = ?
`

_, err = pr.SQLHandler.Exec(query, postID)
if err != nil {
return
}

return
}
14 changes: 10 additions & 4 deletions app/interfaces/user_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ func NewUserController(sqlHandler SQLHandler, logger usecases.Logger) *UserContr
func (uc *UserController) Index(w http.ResponseWriter, r *http.Request) {
uc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)

users, err := uc.UserInteractor.ShowUsers()
users, err := uc.UserInteractor.Index()
if err != nil {
uc.Logger.LogError("%s", err)
// TODO: To return a error response message

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
json.NewEncoder(w).Encode(err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
Expand All @@ -45,10 +48,13 @@ func (uc *UserController) Show(w http.ResponseWriter, r *http.Request) {

userID, _ := strconv.Atoi(r.URL.Query().Get("id"))

user, err := uc.UserInteractor.ShowUserByID(userID)
user, err := uc.UserInteractor.Show(userID)
if err != nil {
uc.Logger.LogError("%s", err)
// TODO: To return a error response message

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
json.NewEncoder(w).Encode(err)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
Expand Down
14 changes: 7 additions & 7 deletions app/interfaces/user_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ type UserRepository struct {
SQLHandler SQLHandler
}

// FindAll return a listing of the resource of users.
// FindAll is returns the number of entities.
func (ur *UserRepository) FindAll() (users domain.Users, err error) {
const query = `
select
SELECT
id,
name
from
FROM
users
`
rows, err := ur.SQLHandler.Query(query)
Expand Down Expand Up @@ -46,15 +46,15 @@ func (ur *UserRepository) FindAll() (users domain.Users, err error) {
return
}

// FindByID return the specified resource of a user.
// FindByID is returns the entity identified by the given id.
func (ur *UserRepository) FindByID(userID int) (user domain.User, err error) {
const query = `
select
SELECT
id,
name
from
FROM
users
where
WHERE
id = ?
`
row, err := ur.SQLHandler.Query(query, userID)
Expand Down
Loading

0 comments on commit b4d3fbd

Please sign in to comment.