What is your best way to structure an app in Go so you can still mock the dependencies and test?
In my app I'm trying to have an internal API ( or service) layer which exposes the functionalities. Then I have a thin layer of HTTP handlers on top to interact with this service layer. This way I can reuse the same Service layer when we start having RPC communications.
Each concern is encapsulated in it's own Service struct, which they all implement the same interface for interchangeability.
The problem is when different services want to talk to one another. If I inject the dependency then I might end up with circular dependencies. Service A uses Service B and Service B calls Service A. As an example a request to Application Service would return all the Users for the requested App. Also a request to User service returns all the Applications the user is associated with.
My thoughts: 1 - Using a Dependency Injection Container and pass it to each Service at initialization.
2 - Using a factory and pass that around which is not much different.
What would be the best Go approach?
Here is a sample structure of the files + sample code:
/**
.
├── dal
│ ├── application.go
│ └── user.go
├── main.go
├── model
│ ├── application.go
│ └── user.go
├── service
│ ├── application.go
│ └── user.go
└── vendor
*/
package model
type Model interface{} // Generic Model interface so all other Models will inherit from
type UserModel struct{
UserID int
Apps []AppModel
}
type AppModel struct{
AppID int
Users []UserModel
}
// DAL
package dal
import (
"model"
)
type DAL interface{
GetByID( id int) model.Model
GetAll(filters map[string]string) []model.Model
}
type AppDal struct{}
func (dal AppDal)GetByID(id int) model.Model {}
func (dal AppDal)GetAll(filters map[string]string) []model.Model {}
type UserDal struct{}
func (dal UserDal)GetByID(id int) model.Model {}
func (dal UserDal)GetAll(filters map[string]string) []model.Model {}
// Services
package service
import (
"model"
"dal"
)
type Service interface{
GetByID (id int) model.Model
GetAll (filters map[string]string) []model.Model
}
type AppService struct{
dal dal.DAL
}
func (s AppService) GetByID(id int) model.Model{
apps := s.dal.GetByID(id)
// Question: How do you inject the userService here
users := userService.GetAll(map[string]string{"ApplicationID": string(id)})
model.AppModel{Users: users}
return
}
func (s AppService) GetAll (filters map[string]string) []model.Model{}
func NewAppService(dal dal.DAL) {
return AppService{dal:dal}
}
type UserService struct{
dal dal.DAL
}
func (s UserService) GetByID(id int) model.Model{
users := s.dal.GetByID(id)
// Question: How do you inject the appservice here
apps := appService.GetAll(map[string]string{"UserID": string(id)})
model.UserModel{Apps: apps}
return
}
func (s UserService) GetAll (filters map[string]string) []model.Model{}
func NewUserService(dal dal.DAL) {
return UserService{dal:dal}
}
// Main
package main
var appDal = AppDal{}
var userDal = UserDal{}
var appService = NewAppService (appDal)
var userService = NewUserService (userDal)
// Should I put all services in a DI Container and pass the DIC to each service. That does not seem right.
// psuedo code here:
http.OnGet("/applications/:id", appService.GetByID)
http.OnGet("/users/:id", userService.GetByID)