├── .gitignore ├── README.md ├── _config.yml ├── controllers ├── PlayerController.go └── PlayerController_test.go ├── infrastructures └── SQLiteHandler.go ├── interfaces ├── IDbHandler.go ├── IPlayerRepository.go ├── IPlayerService.go └── mocks │ ├── IPlayerRepository.go │ └── IPlayerService.go ├── main.go ├── models └── PlayerModel.go ├── repositories └── PlayerRepository.go ├── router.go ├── servicecontainer.go ├── services ├── PlayerService.go └── PlayerService_test.go ├── setup.sql └── viewmodels └── ScoresVM.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | .idea/ 8 | 9 | # Test binary, build with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 16 | .glide/ 17 | 18 | service-pattern-go 19 | vendor/ 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | service-pattern-go 2 | ------- 3 | 4 | Hey! Welcome, this is an example of simple REST API implementation with clean architecture written in Go with complete Dependency Injection along with Mocking example, following SOLID principles. 5 | 6 | Inspired by [Manuel Kiessling go-cleanarchitecture](http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/) and [Joshua Partogi TDD training session](https://github.com/jpartogi/tennis-kata-laravel/) 7 | 8 | It has simple dependencies: 9 | 10 | - [Chi (Router)](https://github.com/go-chi/chi) 11 | - [Testify (Test & Mock framework)](https://github.com/stretchr/testify) 12 | - [Mockery (Mock generator)](https://github.com/vektra/mockery) 13 | - [Hystrix-Go (Circuit Breaker)](https://github.com/afex/hystrix-go) 14 | 15 | Get Started: 16 | 17 | - [Install](https://irahardianto.github.io/service-pattern-go/#install) 18 | - [Introduction](https://irahardianto.github.io/service-pattern-go/#introduction) 19 | - [Folder Structure](https://irahardianto.github.io/service-pattern-go/#folder-structure) 20 | - [Depency Injection](https://irahardianto.github.io/service-pattern-go/#dependency-injection) 21 | - [Mocking](https://irahardianto.github.io/service-pattern-go/#mocking) 22 | - [Testing](https://irahardianto.github.io/service-pattern-go/#testing) 23 | - [Circuit Breaker](https://irahardianto.github.io/service-pattern-go/#circuit-breaker) 24 | 25 | 26 | ---------- 27 | 28 | [Install](https://irahardianto.github.io/service-pattern-go/#install) 29 | ------- 30 | 31 | Clone the source 32 | 33 | git clone https://github.com/irahardianto/service-pattern-go 34 | 35 | Setup dependencies 36 | 37 | go get -u github.com/go-chi/chi 38 | go get -u github.com/jinzhu/gorm 39 | go get github.com/stretchr/testify 40 | go get github.com/vektra/mockery/.../ 41 | go get github.com/afex/hystrix-go/hystrix 42 | go get -u github.com/mattn/go-sqlite3 43 | 44 | Setup sqlite data structure 45 | 46 | sqlite3 /var/tmp/tennis.db < setup.sql 47 | 48 | Test first for your liking 49 | 50 | go test ./... -v 51 | 52 | Run the app 53 | 54 | go build && ./service-pattern-go 55 | 56 | And visit 57 | 58 | http://localhost:8080/getScore/Rafael/vs/Serena 59 | 60 | 61 | ---------- 62 | 63 | [Introduction](https://irahardianto.github.io/service-pattern-go/#introduction) 64 | ------- 65 | This is an example of Go clean architecture implementing Dependency Injection and Mocking for unit testing purposes to achieve safe, reliable and secure source code. 66 | 67 | The idea of the pattern itself is to create decoupled systems that the implementation of lower level domain is not a concern of the implementor, and can be replaced without having concern of breaking implementor function. 68 | 69 | The aim of the architecture is to produce a system that are: 70 | 71 | - Independent of frameworks. The system should be able to become an independent system, not bound into any framework implementation that cause the system to be bloated, instead those framework should be used as a tools to support the system implementation rather than limiting the system capabilities. 72 | - Highly testable. All codes are guilty and tests is the only way we can prove it otherwise, this means that our test coverage has to be able to cover as much layers as we can so we can be sure of our code reliability. 73 | - Independent of database. Business logic should not be bound to the database, the system should be able to swap MySQL, Maria DB, PosgreSQL, Mongo DB, Dynamo DB without breaking the logic. 74 | - Independent of 3rd party library. No 3rd party library should be implemented directly to the system logic, we should abstract in away that our system can replace the library anytime we want. 75 | 76 | Every implementation should only be by using interface, there should be no direct access from the implementor to implementation, that way we can inject its dependency and replace it with mock object during unit tests. For example: 77 | 78 | PlayerService -> implement IPlayerRepository, instead of direct PlayerRepository 79 | 80 | 81 | type PlayerService struct { 82 | interfaces.IPlayerRepository 83 | } 84 | 85 | func (service *PlayerService) GetScores(player1Name string, player2Name string) (string, error) { 86 | 87 | baseScore := [4]string{"Love", "Fifteen", "Thirty", "Forty"} 88 | var result string 89 | 90 | player1, err := service.GetPlayerByName(player1Name) 91 | if err != nil { 92 | //Handle error 93 | } 94 | 95 | player2, err := service.GetPlayerByName(player2Name) 96 | if err != nil { 97 | //Handle error 98 | } 99 | 100 | if player1.Score < 4 && player2.Score < 4 && !(player1.Score+player2.Score == 6) { 101 | 102 | s := baseScore[player1.Score] 103 | 104 | if player1.Score == player2.Score { 105 | result = s + "-All" 106 | } else { 107 | result = s + "-" + baseScore[player2.Score] 108 | } 109 | } 110 | 111 | if player1.Score == player2.Score { 112 | result = "Deuce" 113 | } 114 | 115 | return result, nil 116 | } 117 | 118 | If you look into the implementation of these lines 119 | 120 | player1, err := service.GetPlayerByName(player1Name) 121 | player2, err := service.GetPlayerByName(player2Name) 122 | 123 | Both are actually abstract implementation of the interface, not the real implementation itself. 124 | So later on the Dependency Injection section, we will learn those interface will be injected with the implementation during the compile time. This way, we can switch the implementation of IPlayerService & IPlayerRepository during the injection with whatever implementation without changing the implementation logic. 125 | 126 | Throughout this repo you will find implementation of design patterns such as **Strategy Pattern** when we inject our dependencies with the real implementations. We create **Singleton** and use it to wired up our router and services. We use **Composite** for all our abstract interface implementations so that the implementor can abstractly implement the methods it has, just as the example above where **PlayerService** implements **interfaces.IPlayerRepository** and allows it to directly invoke **GetPlayerByName** which is **IPlayerRepository's** method. We also use **Decorator Pattern** to hook up our circuit breaker without needing to change / modify the original implementation. 127 | 128 | ---------- 129 | 130 | [Folder Structure](https://irahardianto.github.io/service-pattern-go/#folder-structure) 131 | ------- 132 | / 133 | |- controllers 134 | |- infrastructures 135 | |- interfaces 136 | |- models 137 | |- repositories 138 | |- services 139 | |- viewmodels 140 | main.go 141 | router.go 142 | servicecontainer.go 143 | 144 | The folder structure is created to accomodate seperation of concern principle, where every struct should have single responsibility to achieve decoupled system. 145 | 146 | Every folder is a namespace of their own, and every file / struct under the same folder should only use the same namepace as their root folder. 147 | 148 | ### controllers 149 | 150 | controllers folder hosts all the structs under controllers namespace, controllers are the handler of all requests coming in, to the router, its doing just that, business logic and data access layer should be done separately. 151 | 152 | controller struct implement services through their interface, no direct services implementation should be done in controller, this is done to maintain decoupled systems. The implementation will be injected during the compiled time. 153 | 154 | 155 | ### infrasctructures 156 | 157 | infrasctructures folder host all structs under infrasctructures namespace, infrasctructures consists of setup for the system to connect to external data source, it is used to host things like database connection configurations, MySQL, MariaDB, MongoDB, DynamoDB. 158 | 159 | ### interfaces 160 | 161 | interfaces folder hosts all the structs under interfaces namespace, interfaces as the name suggest are the bridge between different domain so they can interact with each other, in our case, this should be the only way for them to interact. 162 | 163 | interface in Go is a bit different then you might already find in other language like Java or C#, while the later implements interface explicitly, Go implements interface implicitly. You just need to implement all method the interface has, and you're good to "Go". 164 | 165 | In our system, our PlayerController implements IPlayerService to be able to interact with the implementation that will be injected. In our case, IPlayerService will be injected with PlayerService. 166 | 167 | The same thing applies on PlayerService which implements IPlayerRepository to be able interact with the injected implementation. In our case, IPlayerRepository will be injected with PlayerRepository during the compile time. 168 | 169 | PlayerRepository on the other hand, will be injected with infrasctructure configuration that has been setup earlier, this ensure that you can change the implementation of PlayerRepository, without changing the implementor which in this case PlayerService let alone break it. The same thing goes to PlayerService and PlayerController relationship, we can refactor PlayerService, we can change it however we want, without touching the implementor which is PlayerController. 170 | 171 | ### models 172 | 173 | models folder hosts all structs under models namespace, model is a struct reflecting our data object from / to database. models should only define data structs, no other functionalities should be included here. 174 | 175 | ### repositories 176 | 177 | repositories folder hosts all structs under repositories namespace, repositories is where the implementation of data access layer. All queries and data operation from / to database should happen here, and the implementor should be agnostic of what is the database engine is used, how the queries is done, all they care is they can pull the data according to the interface they are implementing. 178 | 179 | ### services 180 | 181 | services folder hosts all structs under services namespace, services is where the business logic lies on, it handles controller request and fetch data from data layer it needs and run their logic to satisfy what controller expect the service to return. 182 | 183 | controller might implement many services interface to satisfy the request needs, and controller should be agnostic of how services implements their logic, all they care is that they should be able to pull the result they need according to the interface they implements. 184 | 185 | ### viewmodels 186 | 187 | viewmodels folder hosts all the structs under viewmodels namespace, viewmodels are model to be use as a response return of REST API call 188 | 189 | ### main.go 190 | 191 | main.go is the entry point of our system, here lies the router bindings it triggers ChiRouter singleton and call InitRouter to bind the router. 192 | 193 | ### router.go 194 | 195 | router.go is where we binds controllers to appropriate route to handle desired http request. By default we are using Chi router as it is a light weight router and not bloated with unnecessary unwanted features. 196 | 197 | ### servicecontainer.go 198 | 199 | servicecontainer.go is where the magic begins, this is the place where we injected all implementations of interfaces. Lets cover throughly in the dependency injection section. 200 | 201 | ---------- 202 | 203 | [Dependecy Injection](https://irahardianto.github.io/service-pattern-go/#dependency-injection) 204 | ------- 205 | 206 | Dependecy injection is the heart of TDD, without it we wont be able to do proper TDD because there will be no mocking and we cannot decoupled our code properly. This is one of the misconception when people thinks that they are doing unit testing instead actually they are doing integration test which connects the logic to database. Unit test should be done independently and database should not come in to play when we are doing unit test. One thing to not though, in Go dependency has to be injected during compile time instead of runtime which cause it a bit different than Java / C# implementation, but anyway, its just plain old dependency injection. 207 | 208 | In essence unit test is created to test our logic not our data integrity, and by taking database during unit testing it will add huge complexity to the tests itself, and this creates barrier for programmers new to unit testing as they are struggling to create proper testing for their functions. 209 | 210 | Now why dependency injection is a crucial part in doing proper TDD? the answer lies in the usage of interface. Back when I have never encountered mocking, I always wondering, what is the use of interface, why we should create abstraction for our functions instead of just write it all already, why the hell should we create a duplicate, abstraction that we will be implementing shortly anyway, some says that, because in doing so, your code will be much cleaner and we have proper pattern, I called that bullshit because in essence we dont have to do it if it only for that reason, and I'm still wondering until I learned about mocking. 211 | 212 | Some other people says that interface is used so your program is decoupled, and when needed you can replace the implementations without needing to adjust the implementor. That make sense right? much better than the bullshit. Yea that make sense, we can replace whatever implement whatever interface with whatever. Yea, but how many times would you replace you database connection calls? chances are rare if not never especially if you working on software house that deliver projects after projects after projects, you will never see you component got replaced. 213 | 214 | The when I learned about mocking, all that I have been asking coming to conclusions as if I was like having epiphany, we will discuss more about mocking in the mocking section, but for now lets discuss it in regards of dependency injection usage. So as you see in our project structure, instead of having all component directly talks to each other, we are using interface, take PlayerController for example 215 | 216 | type PlayerController struct { 217 | interfaces.IPlayerService 218 | } 219 | 220 | func (controller *PlayerController) GetPlayerScore(res http.ResponseWriter, req *http.Request) { 221 | 222 | player1Name := chi.URLParam(req, "player1") 223 | player2Name := chi.URLParam(req, "player2") 224 | 225 | scores, err := controller.GetScores(player1Name, player2Name) 226 | if err != nil { 227 | //Handle error 228 | } 229 | 230 | json.NewEncoder(res).Encode(viewmodels.ScoresVM{scores}) 231 | } 232 | 233 | You see that PlayerController uses IPlayerService interface, and since IPlayerService has GetScores method, PlayerController can invoke it and get the result right away. Wait a minute, isn't that the interface is just merely abstraction? so how do it get executed, where is the implementation? 234 | 235 | type IPlayerService interface { 236 | GetScores(player1Name string, player2Name string) (string, error) 237 | } 238 | 239 | You see, instead of calling directly to PlayerService, PlayerController uses the interface of PlayerService which is IPlayerService, there could be many implementation of IPlayerService not just limited to PlayerService it could be BrotherService etc, but how do we determined that PlayerService will be used instead? 240 | 241 | func (k *kernel) InjectPlayerController() controllers.PlayerController { 242 | 243 | sqlConn, _ := sql.Open("sqlite3", "/var/tmp/tennis.db") 244 | sqliteHandler := &infrastructures.SQLiteHandler{} 245 | sqliteHandler.Conn = sqlConn 246 | 247 | playerRepository := &repositories.PlayerRepository{sqliteHandler} 248 | playerService := &services.PlayerService{&repositories.PlayerRepositoryWithCircuitBreaker{playerRepository}} 249 | playerController := controllers.PlayerController{playerService} 250 | 251 | return playerController 252 | } 253 | 254 | This is where dependency injection come in to play, as you see here in servicecontainer.go we are creating **playerController** and inject it with **playerService** as simple as that, this is what dependency injection all about no more. So **playerController's IPlayerService** will be injected by **playerService** along with all implementation that it implements, so for example **GetPlayerByName** now returns whatever **GetPlayerByName** implemented by **playerService** as you can see it in **PlayerService.go** 255 | 256 | Now, how does this relates to TDD & mocking? 257 | 258 | playerService := new(mocks.IPlayerService) 259 | 260 | You see, in PlayerController_test.go we are using mock object to inject the implementation of our service, lets discuss more detail about mocking and testing in each section. 261 | 262 | ---------- 263 | 264 | [Mocking](https://irahardianto.github.io/service-pattern-go/#mocking) 265 | ------- 266 | 267 | Mocking is a concept many times people struggle to understand, let alone implement it, at least I was the one among the one who struggles to understand this concept. But understanding this concept is essential to do TDD. The key point is, we mock dependencies that we need to run our tests, this is why dependency injection is essential to proceed. We are using testfy as our mock library 268 | 269 | Basically what mock object do is replacing injection instead of real implementation with mock as point out at the end of dependency injection session 270 | 271 | playerService := new(mocks.IPlayerService) 272 | 273 | We then create mock GetScores functionalities along with its request and response. 274 | 275 | playerService.On("GetScores", "Rafael", "Serena").Return("Forty-Fifteen", nil) 276 | 277 | As you see, then the mock object is injected to **playerService** of PlayerController, this is why dependency injection is essential to this proses as it is the only way we can inject interface with mock object instead of real implementation. 278 | 279 | playerController := PlayerController{playerService} 280 | 281 | We generate mock our by using vektra mockery for IPlayerService, go to the interfaces folder and then just type. 282 | 283 | mockery -name=IPlayerService 284 | 285 | The output will be inside ```mocks/IPlayerService.go``` and we can use it right away for our testing. 286 | 287 | ---------- 288 | 289 | [Testing](https://irahardianto.github.io/service-pattern-go/#testing) 290 | ------- 291 | 292 | We have cover pretty much everything there is I hope that you already get the idea of proper unit testing and why we should implement interfaces, dependency injection and mocking. The last piece is the unit test itself. 293 | 294 | func TestPlayerScore(t *testing.T) { 295 | 296 | // create an instance of our test object 297 | playerService := new(mocks.IPlayerService) 298 | 299 | // setup expectations 300 | playerService.On("GetScores", "Rafael", "Serena").Return("Forty-Fifteen", nil) 301 | 302 | playerController := PlayerController{playerService} 303 | 304 | // call the code we are testing 305 | req := httptest.NewRequest("GET", "http://localhost:8080/getScore/Rafael/vs/Serena", nil) 306 | w := httptest.NewRecorder() 307 | 308 | r := chi.NewRouter() 309 | r.HandleFunc("/getScore/{player1}/vs/{player2}", playerController.GetPlayerScore) 310 | 311 | r.ServeHTTP(w, req) 312 | 313 | expectedResult := viewmodels.ScoresVM{} 314 | expectedResult.Score = "Forty-Fifteen" 315 | 316 | actualResult := viewmodels.ScoresVM{} 317 | 318 | json.NewDecoder(w.Body).Decode(&actualResult) 319 | 320 | // assert that the expectations were met 321 | assert.Equal(t, expectedResult, actualResult) 322 | } 323 | 324 | As you see here after injecting playerService of playerController with mock object, we are calling the playerController.GetPlayer and simulate request all the way from the router. 325 | 326 | req := httptest.NewRequest("GET", "http://localhost:8080/getScore/Rafael/vs/Serena", nil) 327 | w := httptest.NewRecorder() 328 | 329 | r := chi.NewRouter() 330 | r.HandleFunc("/getScore/{player1}/vs/{player2}", playerController.GetPlayerScore) 331 | 332 | r.ServeHTTP(w, req) 333 | 334 | And assert the result by using testify assertion library 335 | 336 | assert.Equal(t, expectedResult, actualResult) 337 | 338 | ---------- 339 | 340 | [Circuit Breaker](https://irahardianto.github.io/service-pattern-go/#circuit-breaker) 341 | ------- 342 | 343 | Building a distributed system we should really think that everything is not reliable, networks could breaks, servers could suddenly crash, even your 100% unit-tested app could be the root cause of the problems. 344 | 345 | With that in said, when designing distributed system we should keep that in mind, so when some of our system is down, it won't take the whole system. Circuit breaker is a pattern with which we could design our system to be fault-tolerant and can withstand one or more service failure. It should be wrapping all call outside application ex: db call, redis call, api call. 346 | 347 | Essentially circuit breaker works just like electrical circuit breakers, nothing fancy here, the only different is when the breaker is tripped it can be automatically closed when the downstream service is responding properly as described in the picture below. 348 | 349 | ![circuit breaker](https://cdn.pbrd.co/images/GKpFVb1.png) 350 | 351 | In our case, we will be using hystrix-go, it is a go port from Netflix's hystrix library, how it works is essentially the same, even hystrix-go supports turbine along with its hystrix dashboard, but in my case, I rather use the datadog plugins, since we are using datadog to monitor our system. 352 | 353 | For the sake of SOLID principles implementation in our codebase, we will add hystrix-go to our PlayerRepository leveraging decorator pattern, this will maintain our base repository implementation, the one that calls database, clean from modification and we will create its extension which is named PlayerRepositoryWithCircuitBreaker. This is the O part of SOLID which stands for Open for extension, Close for modification. 354 | 355 | 356 | If you recall we inject our PlayerService with PlayerRepositoryWithCircuitBreaker and the original PlayerRepository wrapped inside. 357 | 358 | playerService.PlayerRepository = &repositories.PlayerRepositoryWithCircuitBreaker{playerRepository} 359 | 360 | 361 | Base PlayerRepository implementation : 362 | 363 | type PlayerRepository struct { 364 | interfaces.IDbHandler 365 | } 366 | 367 | func (repository *PlayerRepository) GetPlayerByName(name string) (models.PlayerModel, error) { 368 | 369 | row, err :=repository.Query(fmt.Sprintf("SELECT * FROM player_models WHERE name = '%s'", name)) 370 | if err != nil { 371 | return models.PlayerModel{}, err 372 | } 373 | 374 | var player models.PlayerModel 375 | 376 | row.Next() 377 | row.Scan(&player.Id, &player.Name, &player.Score) 378 | 379 | return player, nil 380 | } 381 | 382 | PlayerRepository extension implementation : 383 | 384 | type PlayerRepositoryWithCircuitBreaker struct { 385 | PlayerRepository interfaces.IPlayerRepository 386 | } 387 | 388 | func (repository *PlayerRepositoryWithCircuitBreaker) GetPlayerByName(name string) (models.PlayerModel, error) { 389 | 390 | output := make(chan models.PlayerModel, 1) 391 | hystrix.ConfigureCommand("get_player_by_name", hystrix.CommandConfig{Timeout: 1000}) 392 | errors := hystrix.Go("get_player_by_name", func() error { 393 | 394 | player, _ := repository.PlayerRepository.GetPlayerByName(name) 395 | 396 | output <- player 397 | return nil 398 | }, nil) 399 | 400 | select { 401 | case out := <-output: 402 | return out, nil 403 | case err := <-errors: 404 | println(err) 405 | return models.PlayerModel{}, err 406 | } 407 | } 408 | 409 | Basically PlayerRepositoryWithCircuitBreaker implement the same interface as PlayerRepository, IPlayerRepository 410 | 411 | type IPlayerRepository interface { 412 | GetPlayerByName(name string) (models.PlayerModel, error) 413 | } 414 | 415 | 416 | As you see here, it is very easy to implement hystrix-go circuit breaker, you just need to wrap your db call inside hystrix if the timeout reached, the circuit breaker will be tripped and all calls to database will be halt, error will be returned instead for future call until db service is up and healthy. 417 | 418 | 419 | Cheers, 420 | M. Ichsan Rahardianto. 421 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /controllers/PlayerController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/irahardianto/service-pattern-go/interfaces" 8 | 9 | "github.com/go-chi/chi" 10 | "github.com/irahardianto/service-pattern-go/viewmodels" 11 | ) 12 | 13 | type PlayerController struct { 14 | interfaces.IPlayerService 15 | } 16 | 17 | func (controller *PlayerController) GetPlayerScore(res http.ResponseWriter, req *http.Request) { 18 | 19 | player1Name := chi.URLParam(req, "player1") 20 | player2Name := chi.URLParam(req, "player2") 21 | 22 | scores, err := controller.GetScores(player1Name, player2Name) 23 | if err != nil { 24 | //Handle error 25 | } 26 | 27 | json.NewEncoder(res).Encode(viewmodels.ScoresVM{scores}) 28 | } 29 | -------------------------------------------------------------------------------- /controllers/PlayerController_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http/httptest" 6 | 7 | "github.com/irahardianto/service-pattern-go/interfaces/mocks" 8 | "github.com/irahardianto/service-pattern-go/viewmodels" 9 | 10 | "testing" 11 | 12 | "github.com/go-chi/chi" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | /* 17 | Actual test functions 18 | */ 19 | 20 | // TestSomething is an example of how to use our test object to 21 | // make assertions about some target code we are testing. 22 | func TestPlayerScore(t *testing.T) { 23 | 24 | // create an instance of our test object 25 | playerService := new(mocks.IPlayerService) 26 | 27 | // setup expectations 28 | playerService.On("GetScores", "Rafael", "Serena").Return("Forty-Fifteen", nil) 29 | 30 | playerController := PlayerController{playerService} 31 | 32 | // call the code we are testing 33 | req := httptest.NewRequest("GET", "http://localhost:8080/getScore/Rafael/vs/Serena", nil) 34 | w := httptest.NewRecorder() 35 | 36 | r := chi.NewRouter() 37 | r.HandleFunc("/getScore/{player1}/vs/{player2}", playerController.GetPlayerScore) 38 | 39 | r.ServeHTTP(w, req) 40 | 41 | expectedResult := viewmodels.ScoresVM{} 42 | expectedResult.Score = "Forty-Fifteen" 43 | 44 | actualResult := viewmodels.ScoresVM{} 45 | 46 | json.NewDecoder(w.Body).Decode(&actualResult) 47 | 48 | // assert that the expectations were met 49 | assert.Equal(t, expectedResult, actualResult) 50 | } 51 | -------------------------------------------------------------------------------- /infrastructures/SQLiteHandler.go: -------------------------------------------------------------------------------- 1 | package infrastructures 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/irahardianto/service-pattern-go/interfaces" 7 | ) 8 | 9 | type SQLiteHandler struct { 10 | Conn *sql.DB 11 | } 12 | 13 | func (handler *SQLiteHandler) Execute(statement string) { 14 | handler.Conn.Exec(statement) 15 | } 16 | 17 | func (handler *SQLiteHandler) Query(statement string) (interfaces.IRow, error) { 18 | //fmt.Println(statement) 19 | rows, err := handler.Conn.Query(statement) 20 | 21 | if err != nil { 22 | fmt.Println(err) 23 | return new(SqliteRow),err 24 | } 25 | row := new(SqliteRow) 26 | row.Rows = rows 27 | 28 | return row, nil 29 | } 30 | 31 | type SqliteRow struct { 32 | Rows *sql.Rows 33 | } 34 | 35 | func (r SqliteRow) Scan(dest ...interface{}) error { 36 | err := r.Rows.Scan(dest...) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (r SqliteRow) Next() bool { 45 | return r.Rows.Next() 46 | } -------------------------------------------------------------------------------- /interfaces/IDbHandler.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | type IDbHandler interface { 4 | Execute(statement string) 5 | Query(statement string) (IRow, error) 6 | } 7 | 8 | type IRow interface { 9 | Scan(dest ...interface{}) error 10 | Next() bool 11 | } -------------------------------------------------------------------------------- /interfaces/IPlayerRepository.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "github.com/irahardianto/service-pattern-go/models" 5 | ) 6 | 7 | type IPlayerRepository interface { 8 | GetPlayerByName(name string) (models.PlayerModel, error) 9 | } 10 | -------------------------------------------------------------------------------- /interfaces/IPlayerService.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | type IPlayerService interface { 4 | GetScores(player1Name string, player2Name string) (string, error) 5 | } 6 | -------------------------------------------------------------------------------- /interfaces/mocks/IPlayerRepository.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0 2 | package mocks 3 | 4 | import mock "github.com/stretchr/testify/mock" 5 | import models "github.com/irahardianto/service-pattern-go/models" 6 | 7 | // IPlayerRepository is an autogenerated mock type for the IPlayerRepository type 8 | type IPlayerRepository struct { 9 | mock.Mock 10 | } 11 | 12 | // GetPlayerByName provides a mock function with given fields: name 13 | func (_m *IPlayerRepository) GetPlayerByName(name string) (models.PlayerModel, error) { 14 | ret := _m.Called(name) 15 | 16 | var r0 models.PlayerModel 17 | if rf, ok := ret.Get(0).(func(string) models.PlayerModel); ok { 18 | r0 = rf(name) 19 | } else { 20 | r0 = ret.Get(0).(models.PlayerModel) 21 | } 22 | 23 | var r1 error 24 | if rf, ok := ret.Get(1).(func(string) error); ok { 25 | r1 = rf(name) 26 | } else { 27 | r1 = ret.Error(1) 28 | } 29 | 30 | return r0, r1 31 | } 32 | -------------------------------------------------------------------------------- /interfaces/mocks/IPlayerService.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0 2 | package mocks 3 | 4 | import mock "github.com/stretchr/testify/mock" 5 | 6 | // IPlayerService is an autogenerated mock type for the IPlayerService type 7 | type IPlayerService struct { 8 | mock.Mock 9 | } 10 | 11 | // GetScores provides a mock function with given fields: player1Name, player2Name 12 | func (_m *IPlayerService) GetScores(player1Name string, player2Name string) (string, error) { 13 | ret := _m.Called(player1Name, player2Name) 14 | 15 | var r0 string 16 | if rf, ok := ret.Get(0).(func(string, string) string); ok { 17 | r0 = rf(player1Name, player2Name) 18 | } else { 19 | r0 = ret.Get(0).(string) 20 | } 21 | 22 | var r1 error 23 | if rf, ok := ret.Get(1).(func(string, string) error); ok { 24 | r1 = rf(player1Name, player2Name) 25 | } else { 26 | r1 = ret.Error(1) 27 | } 28 | 29 | return r0, r1 30 | } 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func main() { 8 | 9 | http.ListenAndServe(":8080", ChiRouter().InitRouter()) 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /models/PlayerModel.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type PlayerModel struct { 4 | Id int 5 | Name string 6 | Score int 7 | } 8 | -------------------------------------------------------------------------------- /repositories/PlayerRepository.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/afex/hystrix-go/hystrix" 5 | "github.com/irahardianto/service-pattern-go/interfaces" 6 | "github.com/irahardianto/service-pattern-go/models" 7 | 8 | _ "github.com/jinzhu/gorm/dialects/sqlite" 9 | "fmt" 10 | ) 11 | 12 | type PlayerRepositoryWithCircuitBreaker struct { 13 | PlayerRepository interfaces.IPlayerRepository 14 | } 15 | 16 | func (repository *PlayerRepositoryWithCircuitBreaker) GetPlayerByName(name string) (models.PlayerModel, error) { 17 | 18 | output := make(chan models.PlayerModel, 1) 19 | hystrix.ConfigureCommand("get_player_by_name", hystrix.CommandConfig{Timeout: 1000}) 20 | errors := hystrix.Go("get_player_by_name", func() error { 21 | 22 | player, _ := repository.PlayerRepository.GetPlayerByName(name) 23 | 24 | output <- player 25 | return nil 26 | }, nil) 27 | 28 | select { 29 | case out := <-output: 30 | return out, nil 31 | case err := <-errors: 32 | println(err) 33 | return models.PlayerModel{}, err 34 | } 35 | } 36 | 37 | type PlayerRepository struct { 38 | interfaces.IDbHandler 39 | } 40 | 41 | func (repository *PlayerRepository) GetPlayerByName(name string) (models.PlayerModel, error) { 42 | 43 | row, err :=repository.Query(fmt.Sprintf("SELECT * FROM player_models WHERE name = '%s'", name)) 44 | if err != nil { 45 | return models.PlayerModel{}, err 46 | } 47 | 48 | var player models.PlayerModel 49 | 50 | row.Next() 51 | row.Scan(&player.Id, &player.Name, &player.Score) 52 | 53 | return player, nil 54 | } 55 | -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/go-chi/chi" 7 | ) 8 | 9 | type IChiRouter interface { 10 | InitRouter() *chi.Mux 11 | } 12 | 13 | type router struct{} 14 | 15 | func (router *router) InitRouter() *chi.Mux { 16 | 17 | playerController := ServiceContainer().InjectPlayerController() 18 | 19 | r := chi.NewRouter() 20 | r.HandleFunc("/getScore/{player1}/vs/{player2}", playerController.GetPlayerScore) 21 | 22 | return r 23 | } 24 | 25 | var ( 26 | m *router 27 | routerOnce sync.Once 28 | ) 29 | 30 | func ChiRouter() IChiRouter { 31 | if m == nil { 32 | routerOnce.Do(func() { 33 | m = &router{} 34 | }) 35 | } 36 | return m 37 | } 38 | -------------------------------------------------------------------------------- /servicecontainer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/irahardianto/service-pattern-go/controllers" 7 | "github.com/irahardianto/service-pattern-go/repositories" 8 | "github.com/irahardianto/service-pattern-go/infrastructures" 9 | "github.com/irahardianto/service-pattern-go/services" 10 | "database/sql" 11 | ) 12 | 13 | type IServiceContainer interface { 14 | InjectPlayerController() controllers.PlayerController 15 | } 16 | 17 | type kernel struct{} 18 | 19 | func (k *kernel) InjectPlayerController() controllers.PlayerController { 20 | 21 | sqlConn, _ := sql.Open("sqlite3", "/var/tmp/tennis.db") 22 | sqliteHandler := &infrastructures.SQLiteHandler{} 23 | sqliteHandler.Conn = sqlConn 24 | 25 | playerRepository := &repositories.PlayerRepository{sqliteHandler} 26 | playerService := &services.PlayerService{&repositories.PlayerRepositoryWithCircuitBreaker{playerRepository}} 27 | playerController := controllers.PlayerController{playerService} 28 | 29 | return playerController 30 | } 31 | 32 | var ( 33 | k *kernel 34 | containerOnce sync.Once 35 | ) 36 | 37 | func ServiceContainer() IServiceContainer { 38 | if k == nil { 39 | containerOnce.Do(func() { 40 | k = &kernel{} 41 | }) 42 | } 43 | return k 44 | } 45 | -------------------------------------------------------------------------------- /services/PlayerService.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/irahardianto/service-pattern-go/interfaces" 5 | ) 6 | 7 | type PlayerService struct { 8 | interfaces.IPlayerRepository 9 | } 10 | 11 | func (service *PlayerService) GetScores(player1Name string, player2Name string) (string, error) { 12 | 13 | baseScore := [4]string{"Love", "Fifteen", "Thirty", "Forty"} 14 | var result string 15 | 16 | player1, err := service.GetPlayerByName(player1Name) 17 | if err != nil { 18 | //Handle error 19 | } 20 | 21 | player2, err := service.GetPlayerByName(player2Name) 22 | if err != nil { 23 | //Handle error 24 | } 25 | 26 | if player1.Score < 4 && player2.Score < 4 && !(player1.Score+player2.Score == 6) { 27 | 28 | s := baseScore[player1.Score] 29 | 30 | if player1.Score == player2.Score { 31 | result = s + "-All" 32 | } else { 33 | result = s + "-" + baseScore[player2.Score] 34 | } 35 | } 36 | 37 | if player1.Score == player2.Score { 38 | result = "Deuce" 39 | } 40 | 41 | return result, nil 42 | } 43 | -------------------------------------------------------------------------------- /services/PlayerService_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/irahardianto/service-pattern-go/interfaces/mocks" 7 | "github.com/irahardianto/service-pattern-go/models" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGetScore(t *testing.T) { 13 | 14 | playerRepository := new(mocks.IPlayerRepository) 15 | 16 | player1 := models.PlayerModel{} 17 | player1.Id = 101 18 | player1.Name = "Rafael" 19 | player1.Score = 3 20 | 21 | player2 := models.PlayerModel{} 22 | player2.Id = 103 23 | player2.Name = "Serena" 24 | player2.Score = 1 25 | 26 | playerRepository.On("GetPlayerByName", "Rafael").Return(player1, nil) 27 | playerRepository.On("GetPlayerByName", "Serena").Return(player2, nil) 28 | 29 | playerService := PlayerService{playerRepository} 30 | 31 | expectedResult := "Forty-Fifteen" 32 | 33 | actualResult, _ := playerService.GetScores("Rafael", "Serena") 34 | 35 | assert.Equal(t, expectedResult, actualResult) 36 | } 37 | -------------------------------------------------------------------------------- /setup.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE player_models (id INTEGER, name VARCHAR(42), score INTEGER); 2 | 3 | INSERT INTO player_models (id, name, score) VALUES (101, "Rafael", 3); 4 | INSERT INTO player_models (id, name, score) VALUES (102, "Roger", 2); 5 | INSERT INTO player_models (id, name, score) VALUES (103, "Serena", 1); 6 | INSERT INTO player_models (id, name, score) VALUES (104, "Maria", 0); 7 | -------------------------------------------------------------------------------- /viewmodels/ScoresVM.go: -------------------------------------------------------------------------------- 1 | package viewmodels 2 | 3 | type ScoresVM struct { 4 | Score string 5 | } 6 | --------------------------------------------------------------------------------