Skip to content

Commit

Permalink
simplify dependency injection implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
irahardianto committed Dec 18, 2017
1 parent 97bd1f2 commit d6fbdfd
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 114 deletions.
148 changes: 49 additions & 99 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Get Started:
- [Install](https://irahardianto.github.io/service-pattern-go/#install)
- [Introduction](https://irahardianto.github.io/service-pattern-go/#introduction)
- [Folder Structure](https://irahardianto.github.io/service-pattern-go/#folder-structure)
- [Naming Convention](https://irahardianto.github.io/service-pattern-go/#naming-convention)
- [Depency Injection](https://irahardianto.github.io/service-pattern-go/#dependency-injection)
- [Mocking](https://irahardianto.github.io/service-pattern-go/#mocking)
- [Testing](https://irahardianto.github.io/service-pattern-go/#testing)
Expand Down Expand Up @@ -79,43 +78,43 @@ PlayerController -> implement IPlayerService, instead of direct PlayerService


type PlayerController struct {
PlayerService interfaces.IPlayerService
PlayerHelper helpers.PlayerHelper
interfaces.IPlayerService
PlayerHelper helpers.PlayerHelper
}

func (controller *PlayerController) GetPlayerScore(res http.ResponseWriter, req *http.Request) {

player1Name := chi.URLParam(req, "player1")
player2Name := chi.URLParam(req, "player2")

scores, err := controller.PlayerService.GetScores(player1Name, player2Name)
if err != nil {
//Handle error
}
response := controller.PlayerHelper.BuildScoresVM(scores)

json.NewEncoder(res).Encode(response)
player1Name := chi.URLParam(req, "player1")
player2Name := chi.URLParam(req, "player2")
scores, err := controller.GetScores(player1Name, player2Name)
if err != nil {
//Handle error
}

response := controller.PlayerHelper.BuildScoresVM(scores)
json.NewEncoder(res).Encode(response)
}

PlayerService -> implement IPlayerRepository, instead of direct PlayerRepository


type PlayerService struct {
PlayerRepository interfaces.IPlayerRepository
interfaces.IPlayerRepository
}

func (service *PlayerService) GetScores(player1Name string, player2Name string) (string, error) {

baseScore := [4]string{"Love", "Fifteen", "Thirty", "Forty"}
var result string

player1, err := service.PlayerRepository.GetPlayerByName(player1Name)
player1, err := service.GetPlayerByName(player1Name)
if err != nil {
//Handle error
}

player2, err := service.PlayerRepository.GetPlayerByName(player2Name)
player2, err := service.GetPlayerByName(player2Name)
if err != nil {
//Handle error
}
Expand All @@ -138,12 +137,14 @@ PlayerService -> implement IPlayerRepository, instead of direct PlayerRepository
return result, nil
}

If you look into the implementation of these lines
If you look into the implementation of this line in PlayerController

scores, err := controller.PlayerService.GetScores(player1Name, player2Name)
scores, err := controller.GetScores(player1Name, player2Name)

player1, err := service.PlayerRepository.GetPlayerByName(player1Name)
player2, err := service.PlayerRepository.GetPlayerByName(player2Name)
And these lines in PlayerService

player1, err := service.GetPlayerByName(player1Name)
player2, err := service.GetPlayerByName(player2Name)

Both are actually abstract implementation of the interface, not the real implementation itself.
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.
Expand Down Expand Up @@ -173,14 +174,6 @@ The folder structure is created to accomodate seperation of concern principle, w

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.

### commands

commands folder hosts all the structs under commands namespace, commands are intended to run console app that don't need interaction like crons and daemon alike.

### configurations

configurations folder hosts all the structs under configurations namespace, the folder should hold configurations of the systems, the environment variables, etc.

### controllers

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.
Expand Down Expand Up @@ -237,43 +230,6 @@ router.go is where we binds controllers to appropriate route to handle desired h

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.


----------

[Naming Convention](https://irahardianto.github.io/service-pattern-go/#naming-convention)
-------
- Folders & Namespaces

Folder naming must be :
1. Descriptive
2. Prefer short than long word

- Files

Files naming must be :
1. Use mixedCaps with first uppercase, eg : DateTime.go, not dateTime.go, String.go not string.go
2. Interface function file name must be follow by "I" character, match with interface function declaration eg : IConnection, IRewardsService
3. Descriptive, file name must be describe what it's functions.
4. Follow SRP (Single Responsibility Priciple), File name is the first time to look before reach the code inside, don't mixed String with DateTime.

- Struct

There are two kind of struct in declaration, first declaring data model and last one wiring things.

- Interface

Interface name must be start with "I" character, for example "IConnection", "IRewardService".

- Variable declaration

Variable names should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex. For other variable use mixedCaps (eg : GlobalVariable, privateVariable)

- Functions

Function names must be as descriptive as possible, don't use long word if not necessary. Same as declaring variable, function name must be use mixedCaps

- Main modules

----------

[Dependecy Injection](https://irahardianto.github.io/service-pattern-go/#dependency-injection)
Expand All @@ -290,23 +246,23 @@ Some other people says that interface is used so your program is decoupled, and
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

type PlayerController struct {
PlayerService interfaces.IPlayerService
PlayerHelper helpers.PlayerHelper
interfaces.IPlayerService
PlayerHelper helpers.PlayerHelper
}

func (controller *PlayerController) GetPlayerScore(res http.ResponseWriter, req *http.Request) {

player1Name := chi.URLParam(req, "player1")
player2Name := chi.URLParam(req, "player2")

scores, err := controller.PlayerService.GetScores(player1Name, player2Name)
if err != nil {
//Handle error
}

response := controller.PlayerHelper.BuildScoresVM(scores)

json.NewEncoder(res).Encode(response)
player1Name := chi.URLParam(req, "player1")
player2Name := chi.URLParam(req, "player2")
scores, err := controller.GetScores(player1Name, player2Name)
if err != nil {
//Handle error
}
response := controller.PlayerHelper.BuildScoresVM(scores)
json.NewEncoder(res).Encode(response)
}

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?
Expand All @@ -323,18 +279,14 @@ You see, instead of calling directly to PlayerService, PlayerController uses the
sqliteHandler := &infrastructures.SQLiteHandler{}
sqliteHandler.Conn = sqlConn

playerRepository := &repositories.PlayerRepository{sqliteHandler}

playerService := &services.PlayerService{}
playerService.PlayerRepository = &repositories.PlayerRepositoryWithCircuitBreaker{playerRepository}

playerController := controllers.PlayerController{}
playerController.PlayerService = playerService
playerRepository := &repositories.PlayerRepository{sqliteHandler}
playerService := &services.PlayerService{&repositories.PlayerRepositoryWithCircuitBreaker{playerRepository}}
playerController := controllers.PlayerController{playerService,helpers.PlayerHelper{}}

return playerController
}

This is where dependency injection come in to play, as you see here in servicecontainer.go we are creating **playerController** and then inject it with **playerService** as simple as that, this is what dependency injection all about no more. So **playerController.PlayerService** interface will be injected by **playerService** along with all implementation that it implements, so for example FindById now returns whatever FindById implemented by **playerService** as you can see it in PlayerService.go
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**

Now, how does this relates to TDD & mocking?

Expand All @@ -347,7 +299,7 @@ You see, in PlayerController_test.go we are using mock object to inject the impl
[Mocking](https://irahardianto.github.io/service-pattern-go/#mocking)
-------

Mocking is a concept many times people struggle to understand, let alone implement it, at least I am the one among the one struggle 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
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

Basically what mock object do is replacing injection instead of real implementation with mock as point out at the end of dependency injection session

Expand All @@ -359,8 +311,7 @@ We then create mock GetScores functionalities along with its request and respons

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.

playerController := PlayerController{}
playerController.PlayerService = playerService
playerController := PlayerController{playerService, helpers.PlayerHelper{}}

We generate mock our by using vektra mockery for IPlayerService, go to the interfaces folder and then just type.

Expand All @@ -383,8 +334,7 @@ We have cover pretty much everything there is I hope that you already get the id
// setup expectations
playerService.On("GetScores", "Rafael", "Serena").Return("Forty-Fifteen", nil)

playerController := PlayerController{}
playerController.PlayerService = playerService
playerController := PlayerController{playerService, helpers.PlayerHelper{}}

// call the code we are testing
req := httptest.NewRequest("GET", "http://localhost:8080/getScore/Rafael/vs/Serena", nil)
Expand Down Expand Up @@ -493,9 +443,9 @@ PlayerRepository extension implementation :

Basically PlayerRepositoryWithCircuitBreaker implement the same interface as PlayerRepository, IPlayerRepository

type IPlayerRepository interface {
GetPlayerByName(name string) (models.PlayerModel, error)
}
type IPlayerRepository interface {
GetPlayerByName(name string) (models.PlayerModel, error)
}


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.
Expand Down
4 changes: 2 additions & 2 deletions controllers/PlayerController.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

type PlayerController struct {
PlayerService interfaces.IPlayerService
interfaces.IPlayerService
PlayerHelper helpers.PlayerHelper
}

Expand All @@ -20,7 +20,7 @@ func (controller *PlayerController) GetPlayerScore(res http.ResponseWriter, req
player1Name := chi.URLParam(req, "player1")
player2Name := chi.URLParam(req, "player2")

scores, err := controller.PlayerService.GetScores(player1Name, player2Name)
scores, err := controller.GetScores(player1Name, player2Name)
if err != nil {
//Handle error
}
Expand Down
4 changes: 2 additions & 2 deletions controllers/PlayerController_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
"github.com/irahardianto/service-pattern-go/helpers"
)

/*
Expand All @@ -27,8 +28,7 @@ func TestPlayerScore(t *testing.T) {
// setup expectations
playerService.On("GetScores", "Rafael", "Serena").Return("Forty-Fifteen", nil)

playerController := PlayerController{}
playerController.PlayerService = playerService
playerController := PlayerController{playerService, helpers.PlayerHelper{}}

// call the code we are testing
req := httptest.NewRequest("GET", "http://localhost:8080/getScore/Rafael/vs/Serena", nil)
Expand Down
9 changes: 3 additions & 6 deletions servicecontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/irahardianto/service-pattern-go/infrastructures"
"github.com/irahardianto/service-pattern-go/services"
"database/sql"
"github.com/irahardianto/service-pattern-go/helpers"
)

type IServiceContainer interface {
Expand All @@ -23,12 +24,8 @@ func (k *kernel) InjectPlayerController() controllers.PlayerController {
sqliteHandler.Conn = sqlConn

playerRepository := &repositories.PlayerRepository{sqliteHandler}

playerService := &services.PlayerService{}
playerService.PlayerRepository = &repositories.PlayerRepositoryWithCircuitBreaker{playerRepository}

playerController := controllers.PlayerController{}
playerController.PlayerService = playerService
playerService := &services.PlayerService{&repositories.PlayerRepositoryWithCircuitBreaker{playerRepository}}
playerController := controllers.PlayerController{playerService,helpers.PlayerHelper{}}

return playerController
}
Expand Down
6 changes: 3 additions & 3 deletions services/PlayerService.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import (
)

type PlayerService struct {
PlayerRepository interfaces.IPlayerRepository
interfaces.IPlayerRepository
}

func (service *PlayerService) GetScores(player1Name string, player2Name string) (string, error) {

baseScore := [4]string{"Love", "Fifteen", "Thirty", "Forty"}
var result string

player1, err := service.PlayerRepository.GetPlayerByName(player1Name)
player1, err := service.GetPlayerByName(player1Name)
if err != nil {
//Handle error
}

player2, err := service.PlayerRepository.GetPlayerByName(player2Name)
player2, err := service.GetPlayerByName(player2Name)
if err != nil {
//Handle error
}
Expand Down
3 changes: 1 addition & 2 deletions services/PlayerService_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ func TestGetScore(t *testing.T) {
playerRepository.On("GetPlayerByName", "Rafael").Return(player1, nil)
playerRepository.On("GetPlayerByName", "Serena").Return(player2, nil)

playerService := PlayerService{}
playerService.PlayerRepository = playerRepository
playerService := PlayerService{playerRepository}

expectedResult := "Forty-Fifteen"

Expand Down

0 comments on commit d6fbdfd

Please sign in to comment.