What is the best approach to have a mockable Golang web app / api structure? (To do Dependency Injection or not)

xuanbao · · 562 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<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&#39;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&#39;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 ( &#34;model&#34; ) 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 ( &#34;model&#34; &#34;dal&#34; ) 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{&#34;ApplicationID&#34;: 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{&#34;UserID&#34;: 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(&#34;/applications/:id&#34;, appService.GetByID) http.OnGet(&#34;/users/:id&#34;, 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&#39;s central package. As an example let&#39;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>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

562 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传