<p>Hey folks,</p>
<p>What is your best way to structure an app in Go so you can still mock the dependencies and test?</p>
<p>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.<br/>
Each concern is encapsulated in it's own Service struct, which they all implement the same interface for interchangeability.</p>
<p>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.</p>
<p>My thoughts:
1 - Using a Dependency Injection Container and pass it to each Service at initialization.</p>
<p>2 - Using a factory and pass that around which is not much different.</p>
<p>What would be the best Go approach?</p>
<p>Here is a sample structure of the files + sample code: </p>
<pre><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)
</code></pre>
<hr/>**评论:**<br/><br/>shovelpost: <pre><p>Using a Java-esque project structure and thinking is IMHO the root of your problems. </p>
<p>What I personally found as a good practice is to keep the types that make sense for the application in the application's central package. As an example let's suppose you are writing a reddit clone with the silly name geddit. Your central application package will be named <code>geddit</code> and you will have <code>geddit.User</code>etc. The first time I did that I thought it was weird but the more I kept using it the better it looked on my code. To my surprise I found <a href="https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1">a recent article</a> recommending that structure so it must have some merit.</p>
<p>Some other good practices are discussed in the articles <em>Structuring Applications in Go</em> <a href="https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091">[1]</a>, <em>Practical Persistence in Go: Organising Database Access</em> <a href="http://www.alexedwards.net/blog/organising-database-access">[2]</a> and <em>Go best practices, six years in</em> <a href="https://peter.bourgon.org/go-best-practices-2016/">[3]</a>.</p>
<p>But before you go all crazy with package structure and organization remember that sometimes the best you can do is go back to the paper and follow the Go proverb: <em>Design the architecture, name the components, document the details.</em> <a href="https://youtu.be/PAAkCSZUG1c?t=18m9s">[4]</a></p></pre>arcagenis: <pre><p><a href="https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1#.2lwwmmr85" rel="nofollow">This</a> helped me a lot.</p></pre>vldo: <pre><p>you seem to be using a domain driven design approach, have a look at <a href="https://github.com/marcusolsson/goddd" rel="nofollow">goddd</a></p></pre>
What is the best approach to have a mockable Golang web app / api structure? (To do Dependency Injection or not)
xuanbao · · 562 次点击这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传