Skip to content

Commit b4d3fbd

Browse files
authored
Merge pull request #8 from bmf-san/feature/v3-0-0
V3.0.0
2 parents c778a04 + 4871ff9 commit b4d3fbd

File tree

13 files changed

+227
-76
lines changed

13 files changed

+227
-76
lines changed

README.md

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# go-api-boilerplate
22
A web application boilerplate built with go and clean architecture.
3+
Most of this application built by standard libraly.
34

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

1112
# Architecture
1213
```
13-
app/
14+
app
1415
├── database
1516
│   ├── migrations
1617
│   │   └── schema.sql
@@ -19,6 +20,7 @@ app/
1920
├── domain
2021
│   ├── post.go
2122
│   └── user.go
23+
├── go_clean_architecture_web_application_boilerplate
2224
├── infrastructure
2325
│   ├── env.go
2426
│   ├── logger.go
@@ -34,47 +36,59 @@ app/
3436
│   ├── access.log
3537
│   └── error.log
3638
├── main.go
37-
├── test.log
3839
└── usecases
3940
├── logger.go
4041
├── post_interactor.go
4142
├── post_repository.go
4243
├── user_interactor.go
4344
└── user_repository.go
45+
46+
8 directories, 22 files
4447
```
4548

46-
| Layer | Directory | Sub directory |
47-
|----------------------|----------------|--------------------------------------|
48-
| Frameworks & Drivers | infrastructure | database, router, env |
49-
| Interface | interfaces | controllers, database, repositories |
50-
| Usecases | usecases | repositories |
51-
| Entities | domain | ex. user.go |
49+
| Layer | Directory |
50+
|----------------------|----------------|
51+
| Frameworks & Drivers | infrastructure |
52+
| Interface | interfaces |
53+
| Usecases | usecases |
54+
| Entities | domain |
5255

53-
# API Document
54-
// TODO: This section will be updated the date when ver.3.0.0 will be released.
56+
# API
5557

58+
| ENDPOINT | HTTP Method | Parameters |
59+
|----------|----------------|---------------|
60+
| /users | GET | |
61+
| /user | GET | ?id=[int] |
62+
| /posts | GET | |
63+
| /post | POST | |
64+
| /post | DELETE | ?id=[int] |
5665

5766
# Controller method naming rule
58-
Use these word as prefix.
5967

60-
- index
61-
- Display a listing of the resource.
62-
- show
63-
- Display the specified resource.
64-
- store
65-
- Store a newly created resource in storage.
66-
- update
67-
- Update the specified resource in storage.
68-
- destory
69-
- Remove the specified resource from storage.
68+
| Controller Method | HTTP Method | Description |
69+
|-------------------|-------------|--------------------------------------------|
70+
| Index | GET | Display a listing of the resource |
71+
| Store | POST | Store a newly created resource in storage |
72+
| Show | GET | Display the specified resource |
73+
| Update | PUT/PATCH | Update the specified resource in storage |
74+
| Destroy | DELETE | Remove the specified resource from storage |
7075

7176
# Repository method naming rule
72-
Use these word as prefix.
7377

74-
- get
75-
- add
76-
- update
77-
- delete
78+
| Repository Method | Description |
79+
|-------------------|------------------------------------------------------|
80+
| FindByXX | Returns the entity identified by the given XX |
81+
| FindAll | Returns all entities |
82+
| Save | Saves the given entity |
83+
| SaveByXX | Saves the given entity identified by the given XX |
84+
| DeleteByXX | Deletes the entity identified by the given XX |
85+
| Count | Returns the number of entities |
86+
| ExistsBy | Indicates whether an entity with the given ID exists |
87+
88+
cf. [Spring Data JPA - Reference Documentation](https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.core-concepts)
89+
90+
# Tests
91+
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.
7892

7993
# References
8094
- [github - manuelkiessling/go-cleanarchitecture](https://github.com/manuelkiessling/go-cleanarchitecture)

app/infrastructure/router.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ import (
66

77
"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/interfaces"
88
"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/usecases"
9+
"github.com/go-chi/chi"
910
)
1011

1112
// Dispatch is handle routing
12-
func Dispatch(logger usecases.Logger, sqlHandler interfaces.SQLHandler, mux *http.ServeMux) {
13+
func Dispatch(logger usecases.Logger, sqlHandler interfaces.SQLHandler) {
1314
userController := interfaces.NewUserController(sqlHandler, logger)
1415
postController := interfaces.NewPostController(sqlHandler, logger)
1516

16-
// TODO: Maybe I'll implement a routing package ...
17-
mux.HandleFunc("/users", userController.Index)
18-
mux.HandleFunc("/user", userController.Show)
19-
mux.HandleFunc("/post", postController.Store)
17+
r := chi.NewRouter()
18+
r.Get("/users", userController.Index)
19+
r.Get("/user", userController.Show)
20+
r.Get("/posts", postController.Index)
21+
r.Post("/post", postController.Store)
22+
r.Delete("/post", postController.Destroy)
2023

21-
if err := http.ListenAndServe(":"+os.Getenv("SERVER_PORT"), mux); err != nil {
24+
if err := http.ListenAndServe(":"+os.Getenv("SERVER_PORT"), r); err != nil {
2225
logger.LogError("%s", err)
2326
}
2427
}

app/interfaces/post_controller.go

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package interfaces
33
import (
44
"encoding/json"
55
"net/http"
6+
"strconv"
67

8+
"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/domain"
79
"github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/usecases"
810
)
911

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

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

32-
Post, err := uc.PostInteractor.StorePost()
34+
posts, err := pc.PostInteractor.Index()
3335
if err != nil {
34-
uc.Logger.LogError("%s", err)
35-
// TODO: To return a error response message
36+
pc.Logger.LogError("%s", err)
37+
38+
w.Header().Set("Content-Type", "application/json")
39+
w.WriteHeader(500)
40+
json.NewEncoder(w).Encode(err)
41+
}
42+
w.Header().Set("Content-Type", "application/json")
43+
json.NewEncoder(w).Encode(posts)
44+
}
45+
46+
// Store is stora a newly created resource in storage.
47+
func (pc *PostController) Store(w http.ResponseWriter, r *http.Request) {
48+
pc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
49+
50+
p := domain.Post{}
51+
err := json.NewDecoder(r.Body).Decode(&p)
52+
if err != nil {
53+
pc.Logger.LogError("%s", err)
54+
55+
w.Header().Set("Content-Type", "application/json")
56+
w.WriteHeader(500)
57+
json.NewEncoder(w).Encode(err)
58+
}
59+
60+
post, err := pc.PostInteractor.Store(p)
61+
if err != nil {
62+
pc.Logger.LogError("%s", err)
63+
64+
w.Header().Set("Content-Type", "application/json")
65+
w.WriteHeader(500)
66+
json.NewEncoder(w).Encode(err)
67+
}
68+
w.Header().Set("Content-Type", "application/json")
69+
json.NewEncoder(w).Encode(post)
70+
}
71+
72+
// Destroy is remove the specified resource from storage.
73+
func (pc *PostController) Destroy(w http.ResponseWriter, r *http.Request) {
74+
pc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
75+
76+
postID, _ := strconv.Atoi(r.URL.Query().Get("id"))
77+
78+
err := pc.PostInteractor.Destroy(postID)
79+
if err != nil {
80+
pc.Logger.LogError("%s", err)
81+
82+
w.Header().Set("Content-Type", "application/json")
83+
w.WriteHeader(500)
84+
json.NewEncoder(w).Encode(err)
3685
}
3786
w.Header().Set("Content-Type", "application/json")
38-
json.NewEncoder(w).Encode(Post)
87+
json.NewEncoder(w).Encode(nil)
3988
}

app/interfaces/post_repository.go

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,69 @@
11
package interfaces
22

3+
import "github.com/bmf-san/go-clean-architecture-web-application-boilerplate/app/domain"
4+
35
// A PostRepository belong to the inteface layer
46
type PostRepository struct {
57
SQLHandler SQLHandler
68
}
79

8-
// AddPost add a newly created resource of a Post.
9-
func (ur *PostRepository) AddPost() (id int64, err error) {
10-
// NOTE: this is a transaction example.
11-
tx, err := ur.SQLHandler.Begin()
10+
// FindAll is returns all entities.
11+
func (pr *PostRepository) FindAll() (posts domain.Posts, err error) {
12+
const query = `
13+
SELECT
14+
id,
15+
user_id,
16+
body
17+
FROM
18+
posts
19+
`
20+
21+
rows, err := pr.SQLHandler.Query(query)
22+
23+
defer rows.Close()
24+
1225
if err != nil {
1326
return
1427
}
1528

16-
const query = `
17-
insert into
18-
posts(id, user_id, body)
19-
values
20-
(?, ?, ?)
21-
`
29+
for rows.Next() {
30+
var id int
31+
var userID int
32+
var body string
33+
if err = rows.Scan(&id, &userID, &body); err != nil {
34+
return
35+
}
36+
post := domain.Post{
37+
ID: id,
38+
UserID: userID,
39+
Body: body,
40+
}
41+
posts = append(posts, post)
42+
}
2243

23-
_, err = tx.Exec(query, 1, 1, "hoge")
24-
if err != nil {
44+
if err = rows.Err(); err != nil {
2545
return
2646
}
2747

28-
result, err := tx.Exec(query, 100, 1, "Hello, World. This is newly inserted.")
48+
return
49+
}
50+
51+
// Save is saves the given entity.
52+
func (pr *PostRepository) Save(p domain.Post) (id int64, err error) {
53+
// NOTE: this is a transaction example.
54+
tx, err := pr.SQLHandler.Begin()
2955
if err != nil {
3056
return
3157
}
3258

59+
const query = `
60+
INSERT INTO
61+
posts(user_id, body)
62+
VALUES
63+
(?, ?)
64+
`
65+
66+
result, err := tx.Exec(query, p.UserID, p.Body)
3367
if err != nil {
3468
_ = tx.Rollback()
3569
return
@@ -46,3 +80,21 @@ func (ur *PostRepository) AddPost() (id int64, err error) {
4680

4781
return
4882
}
83+
84+
// DeleteByID is deletes the entity identified by the given id.
85+
func (pr *PostRepository) DeleteByID(postID int) (err error) {
86+
const query = `
87+
DELETE
88+
FROM
89+
posts
90+
WHERE
91+
id = ?
92+
`
93+
94+
_, err = pr.SQLHandler.Exec(query, postID)
95+
if err != nil {
96+
return
97+
}
98+
99+
return
100+
}

app/interfaces/user_controller.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ func NewUserController(sqlHandler SQLHandler, logger usecases.Logger) *UserContr
3030
func (uc *UserController) Index(w http.ResponseWriter, r *http.Request) {
3131
uc.Logger.LogAccess("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
3232

33-
users, err := uc.UserInteractor.ShowUsers()
33+
users, err := uc.UserInteractor.Index()
3434
if err != nil {
3535
uc.Logger.LogError("%s", err)
36-
// TODO: To return a error response message
36+
37+
w.Header().Set("Content-Type", "application/json")
38+
w.WriteHeader(500)
39+
json.NewEncoder(w).Encode(err)
3740
}
3841
w.Header().Set("Content-Type", "application/json")
3942
json.NewEncoder(w).Encode(users)
@@ -45,10 +48,13 @@ func (uc *UserController) Show(w http.ResponseWriter, r *http.Request) {
4548

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

48-
user, err := uc.UserInteractor.ShowUserByID(userID)
51+
user, err := uc.UserInteractor.Show(userID)
4952
if err != nil {
5053
uc.Logger.LogError("%s", err)
51-
// TODO: To return a error response message
54+
55+
w.Header().Set("Content-Type", "application/json")
56+
w.WriteHeader(500)
57+
json.NewEncoder(w).Encode(err)
5258
}
5359
w.Header().Set("Content-Type", "application/json")
5460
json.NewEncoder(w).Encode(user)

app/interfaces/user_repository.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ type UserRepository struct {
99
SQLHandler SQLHandler
1010
}
1111

12-
// FindAll return a listing of the resource of users.
12+
// FindAll is returns the number of entities.
1313
func (ur *UserRepository) FindAll() (users domain.Users, err error) {
1414
const query = `
15-
select
15+
SELECT
1616
id,
1717
name
18-
from
18+
FROM
1919
users
2020
`
2121
rows, err := ur.SQLHandler.Query(query)
@@ -46,15 +46,15 @@ func (ur *UserRepository) FindAll() (users domain.Users, err error) {
4646
return
4747
}
4848

49-
// FindByID return the specified resource of a user.
49+
// FindByID is returns the entity identified by the given id.
5050
func (ur *UserRepository) FindByID(userID int) (user domain.User, err error) {
5151
const query = `
52-
select
52+
SELECT
5353
id,
5454
name
55-
from
55+
FROM
5656
users
57-
where
57+
WHERE
5858
id = ?
5959
`
6060
row, err := ur.SQLHandler.Query(query, userID)

0 commit comments

Comments
 (0)