Golang package/directory conventions for Interfaces and their implementations?

agolangf · · 461 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Let&#39;s consider your typical web application. In an MVC application, you may eventually want to introduce a &#34;Service&#34; layer that abstracts complex business logic such as user registration. So in your controller, you&#39;d pass an instance of a <code>services.User</code> struct and simply call the <code>Register()</code> method on it. </p> <p>Now, if <code>services.User</code> was simply a struct, we could have a relatively simple source code structure, like so:</p> <pre><code>- [other directories here]/ - services/ - user.go - [other service structs here] - main.go </code></pre> <p>And the <code>services/user.go</code> would look like so : </p> <pre><code>package services type User struct { ... } func NewUserService(){ ... } func (u User) Register() { ... } </code></pre> <p>Which is all reasonably easy to read, so far. Let&#39;s say we take it one step further. In the spirit of making our web app easily testable, we&#39;ll turn all our Service structs into Service interfaces. That way, we can easily mock them for unit tests. For that purpose, we&#39;ll create a &#34;AppUser&#34; struct (for use in the actual application) and a &#34;MapUser&#34; struct (for mocking purposes). Placing the interfaces and the implementations in the same <code>services</code> directory makes sense - they&#39;re all still <code>service</code> code, after all.</p> <p>Our <code>services</code> folder now looks like this:</p> <pre><code>- services/ - app_user.go // the AppUser struct - [other services here] - map_user.go // the MapUser struct - [other services here] - user.go // the User interface - [other service structs here] </code></pre> <p>As you can tell, this makes the <code>services</code> package and directory a lot more difficult to handle - you can easily imagine how chaotic it would look with a dozen different interfaces, each of which at least have at least 1 implementation. If I change the <code>User</code> interface in <code>user.go</code>, I&#39;d have to dart all across the directory listing to find all it&#39;s implementations to change, which is not at all ideal.</p> <p>Additionally, it becomes pretty crazy when you type <code>services.New(...)</code> and you&#39;re greeted with perhaps 50 or so autocomplete suggestions ; the <code>services</code> package has become nothing but a shambling monster.</p> <p>One of the simplest ideas I had to solve this is to go against convention and embrace repetition: </p> <pre><code>- services/ - userService/ - app.go // the AppUser struct - map.go // the MapUser struct - interface.go // the User interface - [other services here] </code></pre> <p>This keeps all the UserService related code in a logical, self contained package. But having to constantly refer to <code>userService.UserService</code> is pretty darn ugly.</p> <p>I&#39;ve looked at all kinds of web application templates, and none of them (beyond the ones that are incredibly barebones) have an elegant solution to this structural. Most (if not all) of them simply omit interfaces completely to solve it, which is unacceptable. </p> <p>Any tips or hints?</p> <hr/>**评论:**<br/><br/>Morgahl: <pre><p>What you have described here is generally called <code>stutter</code> and does definitely look ugly. The <code>go vet</code> tool will even warn you about it with the one exception of your example above. </p></pre>Aetheus: <pre><p>Yeah, I&#39;m aware of that. It is pretty ugly, I agree. But the alternative is to have a chaotic <code>services</code> directory in this case. Placing interface and implementation code in the same directory makes sense in most other programming languages (since they&#39;d probably live in their own self-contained subdirectory/subpackage), but trying to do so here while following Go&#39;s conventions causes directories to inflate to ridiculous sizes and become unreadable. </p> <p>What, then, is the &#34;Go way&#34; of avoiding such bloat while still following conventions? </p></pre>Morgahl: <pre><p>Your current <code>userService.UserService</code> is actually idiomatic, though I would see <a href="/u/Slythe2o0" rel="nofollow">/u/Slythe2o0</a> &#39;s reply for a more concise way. The issues with stutter come about when you do things like <code>userService.NewUserService</code> or <code>userService.UserServiceRegister</code>. </p></pre>Sythe2o0: <pre><p>So remove &#34;service&#34; from the subdirectory types and packages.</p> <pre><code>- services/ - user/ </code></pre> <p>Then it&#39;s <code>user.User</code>, which is generally an accepted form of stutter, and is also short.</p></pre>Aetheus: <pre><p>Doesn&#39;t that lose the &#34;context&#34; of the package, then? Glancing through the code quickly, a casual reader wouldn&#39;t know if the <code>user</code> package was referring to a <code>service</code>, or a <code>model</code>, or a <code>template</code> or a <code>controller</code>. </p> <p>The naming conventions work reasonably well for libraries ( an imaginary &#34;http.Router()&#34; is fairly self explanatory and &#34;pretty&#34; code ), but I haven&#39;t actually figured out how to make code look good in web application code.</p></pre>drvd: <pre><p>Your question implies that a package like <code>user</code> should refer to either a service or a model or ... and never to all of them. But this is common in Go: What&#39;s related is grouped in a package.</p></pre>beekay24: <pre><p>here&#39;s what i do:</p> <pre><code>/api /user.go /store /user.go /schema /user.go </code></pre> <p>/schema defines all the relevant structs (with the appropriate tags) that my service will use. - this includes the data that is exchanged between my service and db as well as the data from service to http response. Usually they are the same, but on some occasions you might want to do in-memory aggregations. As an example...</p> <pre><code>type User struct { Name string `json:&#34;name&#34; bson:&#34;name&#34;` } type AllUsersResponse struct { Count int `json:&#34;count&#34;` Users []User `json:&#34;users&#34;` } </code></pre> <p>/store defines the Interface for all the interactions that my service supports</p> <pre><code>type Store interface { GetAllUsers() ([]*schema.User, error) GetUser(name string) (*schema.User, error) } type MongoStore struct { session *mgo.Session } func (m *MongoStore) GetUsers() ([]*schema.User, error) { // m.session.C(&#34;users&#34;).Find({}) } func (m *MongoStore) GetUser(name string) (*AllUsersResponse, error) { // m.session.C(&#34;users&#34;).Find({&#34;name&#34;: name}) } </code></pre> <p>/api defines the routes that my service will support (i.e. using echo)</p> <pre><code>func SetupRoutes(e *echo.Echo) { e.GET(&#34;/api/users&#34;, func(c echo.Context) error { // users, err := store.GetUsers() }) e.GET(&#34;/api/users/:name&#34;, func(c echo.Context) error { // users, err := store.GetUser(e.Param(&#34;name&#34;)) }) } </code></pre> <p>Of course I&#39;m severely oversimplifying the code examples but what I&#39;m hoping you&#39;ll pick up from this is a clear division of layers in data transfer when you organize your packages this way...</p></pre>

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

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