Don't stutter: how to name packages so as to avoid stuttering?

xuanbao · · 736 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I have separated my app into two main packages:</p> <ul> <li><p>myapp/model</p> <ul> <li>myapp/model/user</li> <li>myapp/model/post</li> <li>myapp/model/comment</li> </ul></li> <li><p>myapp/service</p> <ul> <li>myapp/service/user</li> <li>myapp/service/post</li> <li>myapp/service/comment</li> </ul></li> </ul> <p>But this leads to stuttering when using the model: <code>user.User{}</code> or leads to having to rename an import when using model and service:</p> <pre><code>import ( userModel &#34;myapp/service/user&#34; userService &#34;myapp/service/user&#34; ) </code></pre> <p>My models are generated from the database, so it&#39;d be cumbersome to add each model to the their corresponding service package. I&#39;m looking for ideas.</p> <hr/>**评论:**<br/><br/>peterbourgon: <pre><blockquote> <p>A common practise from other languages is to organize types together in a package called models or types. In Go, we organize code by their functional responsibilities.</p> <p>Rather than creating a models package and declare all entity types there, a User type should live in a service-layer package.</p> </blockquote> <p><a href="https://rakyll.org/style-packages/">https://rakyll.org/style-packages/</a></p></pre>drink_with_me_to_day: <pre><p>I&#39;ve read that recently, but in my case the models are generated, and having to manually add them to each service package would be wasted time.</p> <p>If I was hand-coding the models, like in other projects, it&#39;s very easy to follow the convention. I&#39;m wondering how others deal with generated code and package naming/structuring.</p></pre>peterbourgon: <pre><p>I don&#39;t know your exact context, so there may be something unique to your situation that overrides this instinct of mine. With that said: I think you are over-valuing the time it takes to type things initially, and under-valuing the time wasted in the future trying to derive meaning from generated <code>model</code> and/or <code>service</code> packages. Biting the bullet and arranging things idiomatically is almost always the right move, for any project that&#39;s gonna live more than a few months, or be worked on by more than 1 person.</p></pre>pancakesausagestick: <pre><p>I can&#39;t agree more with this. As an architectural consideration, if your model is &#34;auto generated&#34; and you&#39;re not adding functionality to it then you have an anemic domain model [Fowler]. IOW you don&#39;t have a model at all, just a pile of data types surrounded by cruft. If these structures are sealed up in their own package and no hand-written code can go in there, then you have to Publicly export all your symbols for other packages to do the real work. You&#39;re doing zero encapsulation, so why split it up into separate packages anyway?</p> <p>The problem with treating packages like folders for you to organize your code with is that you are prematurely drawing boundaries in your system for no good reason. Packages are tools of encapsulation first, and should be named according to their function. They provide a barrier of abstraction. If you organize them with a preconceived hygienic hierarchy instead of actually how your code works, you&#39;re going to be working cross purpose. OP&#39;s question is about namespaces. Packages are more than just namespaces.</p></pre>jackmcmorrow: <pre><p>I once read all projects have at least 2 people: you, and six days/weeks/months/years you. Wich makes sense, and MVC helped me a lot in my projects.</p></pre>mcandre: <pre><p>A good model generator allows the user to specify the package</p></pre>aboukirev: <pre><p>Keep in mind that you can easily split and rearrange code within a package between files, i.e. generated code goes into user_gen.go while hand-written code goes into user.go in the same package.</p></pre>baonh: <pre><p>Does this conflict with the clean architecture? How about define the User entity in the user package, and in the service package, you can embed or assemble the User entity to the UserResponse type, and return that to the api response?</p></pre>shovelpost: <pre><blockquote> <p>My models are generated from the database, so it&#39;d be cumbersome to add each model to the their corresponding service package. </p> </blockquote> <p>Either tweak your generator to produce &#34;idiomatic&#34; packages (aka no more model, service etc.) or do it manually.</p> <blockquote> <p>I&#39;m looking for ideas.</p> </blockquote> <p>I think a better way is to keep all your &#34;models&#34; under a package named after your app (or at the root of the repo) as shown in <a href="https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1">Standard Package Layout</a> and <a href="https://github.com/upspin/upspin">Upspin</a>.</p> <ul> <li>myapp/myapp</li> </ul> <p>This makes the naming better e.g. <code>myapp.User</code>, avoids stuttering, import circles and keeps things simple.</p></pre>ledongthuc1: <pre><p>just</p> <pre><code>myapp/model/user.go myapp/model/post.go myapp/model/comment.go myapp/service/user.go myapp/service/post.go myapp/service/comment.go </code></pre> <p>I don&#39;t think your model and services are big enough to separate with different package</p></pre>drink_with_me_to_day: <pre><p>In my case they have to be in separated packages because my columns are generated as types. So different tables would have types with the same names as other tables.</p> <pre><code>table user ( id uuid name varchar(255) // etc ) table resource ( id uuid name varchar(255) // etc ) </code></pre> <p>Those two tables would both generate:</p> <pre><code>type TableName struct { ID ID Name Name } type ID uuid.UUID type Name string </code></pre></pre>jerf: <pre><p>Generally speaking, having those two tables share the same types when possible would strike most of us as a feature, not a bug. What generator are you using? Perhaps it needs to be made smarter to examine the environment for whether a type already exists rather than blindly generating it.</p></pre>drink_with_me_to_day: <pre><p>Well, those types implement a <code>Column</code> interface, with column specific validation and table data for easier custom SQL generation.</p> <p>The column interface is something like: </p> <pre><code>type Column interface { Name() string PrimaryKey() (PK, bool) ForeignKey() []FK Table() Table Constraints() []Constraint } And a validator interface as well: type Validator interface { Validate() error } </code></pre> <p>The generator is one of my own, I&#39;m still trying to sort things out.</p></pre>jerf: <pre><p>Yeah, I would definitely suggest the generator needs to be able to use pre-existing types that it doesn&#39;t insist on generating itself. You&#39;ll want that for other reasons.</p> <p>I would also suggest that you read up on what it means to be a <a href="https://en.wikipedia.org/wiki/Hygienic_macro" rel="nofollow">hygienic macro</a>. It may not be immediately obvious to you why I suggest this, since it will be discussed in terms of languages other than Go, but it is a generalized problem that code generators face. It will help you understand the landscape you are working in. (You face the additional problem that you want your identifiers to be human-readable and useful, which adds its own additional annoying issues.)</p></pre>very-little-gravitas: <pre><p>I don&#39;t think there is one good package layout for commands, web apps and services, <em>it depends on the app</em> and you should be guided by what is useful, not what others do. One thing to be careful of is imports - try to minimise imports between packages, and have some guidelines for which packages import which others (for example packages lower in your hierarchy probably shouldn&#39;t import higher level ones). </p> <p>Another approach to this is not to divide by functional responsibilities or in an mvc scheme, but by resource - something like this:</p> <ul> <li>/myapp/user <ul> <li>/myapp/user/actions</li> <li>/myapp/user/views</li> </ul></li> </ul> <p>The advantage to that is it is immediately apparent in a web app exactly where all the functionality is to do with users and you don&#39;t have to skip around several folders to make a change. </p> <p>Re the stuttering here - it depends how often you&#39;re using user.User{} in your code - if you use constructors to set default values or to pull from the db normally (user.New() or user.Find(), or perhaps users.Find()) you won&#39;t actually see stuttering like this much at all. So it may not be a problem for you. </p> <p>Re duplicated imports, I agree that&#39;s not nice, but you don&#39;t have to name the package the same as the folder - that&#39;s just a convention. You could use the name service for service/user if you wish for example. </p> <p>More of a problem is circular imports - if you divide your packages up to have models separate from each other be aware this means you can&#39;t have a user model referencing pages for example - in some ways this imposes a useful discipline though - only those importing both models can use them, and models don&#39;t become intertwined and interdependent. </p></pre>egonelbre: <pre><pre><code>myapp/user myapp/post myapp/comment </code></pre></pre>palitones: <pre><p>I&#39;m fairly unexperienced in Go, coming from PHP. What I&#39;ve learned is that unlike PHP&#39;s namespace, that is based both on folders and files, GO&#39;s is only folder based.</p> <p>I&#39;d just have a models folder and a service one, no subdirs. </p></pre>rotharius: <pre><p>In PHP, namespaces are only a label. They are bound to file/folder location because of autoloading conventions, i.e. composer&#39;s autoloader.</p> <p>Whether Go or PHP, I usually subdivide my directory structure in a way that tells me something about the architecture and provides a separation of concerns regarding the domain, (infrastructural) implementation details and orchestration code.</p></pre>epiris: <pre><p>I made a small comment about project <a href="https://www.reddit.com/r/golang/comments/7hhq45/advice_on_a_good_local_development_workflow/dqr4mp5/" rel="nofollow">workflow</a> and <a href="https://www.reddit.com/r/golang/comments/7hhq45/advice_on_a_good_local_development_workflow/dqs0s4n/" rel="nofollow">structure</a> not that long ago. I&#39;ll add to that list what I&#39;m doing for types and services, which just evolved from trial and error or copying conventions from big projects. Most contentious thing in here is a &#34;pkg&#34; dir in a applications repo. Anything that has a main with more than 1-3 support packages I think justifies having a pkg dir. Moved to a <a href="https://gist.githubusercontent.com/cstockton/dc604090c51c2437de732b4942c70d02/raw/b6c574aedc28b0100230ae429ff95b81e6dd5846/reddit-comment.txt" rel="nofollow">gist</a> since it was a wall of text.</p></pre>ishbits: <pre><p>I’m interested in this layout. Any public repos you can point me to that use it?</p></pre>jerf: <pre><p>While stuttering is bad, there isn&#39;t really a way to avoid <code>user.User{}</code>, without the probably-worse problem of importing <code>user</code> with <code>.</code> so you can just say <code>User{}</code>. I think avoiding stuttering is more about compound phases like <code>getuser.GetUserFromDB()</code>, where (ignoring the problematic module name) you want <code>getuser.FromDB()</code>.</p> <p>Though if I were in your position, I would consider whether I am getting any benefit from this division. I&#39;m probably on the crazier end of the Go community where I advocate (and implement) a lot more packages and heavier use of package separation than most other programmers, but if you&#39;re going to pay the price for the separation you need to be getting some benefit, too. If these things are being autogenerated, are there any private members or methods on any of these objects? If not, if they are all just public, I would consider just piling them all into a single package <code>datamodel</code> or something, so you get <code>datamodel.User</code>. (Would shy away from &#34;data&#34; or &#34;model&#34; alone as the name just on the general principle that it&#39;s overloaded and easy to get naming collisions.) Separate packages need to pay for themselves somehow to be worth it.</p></pre>

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

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