Recommended patterns for database functions

polaris · · 1493 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Possibly not the best title as I&#39;m struggling to put what I&#39;m looking for into words. I want to know how I should be structuring the files in my web app project that interact with the database, simple CRUD functions for different entities (e.g. Users, Posts, etc.)</p> <p>I read this <a href="https://www.reddit.com/r/golang/comments/38hkor/go_best_practice_for_accessing_database_in/">post</a> and a few other articles which have given me some what of a starting point, but I still feel there&#39;s room for improvement. </p> <p>I want to use the <a href="https://github.com/jinzhu/gorm">GORM</a> ORM library, although I&#39;m open to being convinced otherwise.</p> <p>My current pattern is as follows for my repositories package:</p> <p>base.go</p> <pre><code>package repositories import &#34;github.com/jinzhu/gorm&#34; //Base defines a base respository type Base struct { DB *gorm.DB } </code></pre> <p>Pretty standard... Currently just a struct to be <em>inherited?</em> by my repositories so they each have a DB field.</p> <p>user.go</p> <pre><code>package repositories import &#34;bitbucket.org/foo/bar/models&#34; //User defines the user repository type User struct { Base } //ReadID finds a user from the DB with the supplied ID func (r *User) Read(id uint) *models.User { var user *models.User r.DB.Where(&#34;id = ?&#34;, id).First(&amp;user) return user } //ReadAll gets all users func (r *User) ReadAll() []*models.User { var users []*models.User r.DB.Find(&amp;users) return users } </code></pre> <p>Then as you see each repository has the functions you&#39;d expect.</p> <p>Obvious issue I have is each repository is gonna have duplicate functions: Read, ReadAll, Create, Remove etc. Is there a way to make this more generic and achieve the same result? </p> <p>Any advice on either my current problem or best practice development patterns in general would be greatly appreciated.</p> <hr/>**评论:**<br/><br/>klaaax: <pre><p>Don&#39;t return entities from your methods, return an error and just use nil pointers as an argument ([]*User and *User for instance) to get the results you want. That way you only need 1 repository.</p> <pre><code> //ReadAll entities func (r repository) ReadAll(pointerOfEntities interface{})error{ return r.DB.Find(pointerOfEntities) } </code></pre> <blockquote> <p>BUT BUT static typing ?</p> </blockquote> <p>Well you can use an interface to discriminate types instead of using empty interface. It may sound like a trick it is one way to retain some type safety.</p></pre>danhardman: <pre><p>This is what I was hoping to get, I did think of it although I was worried it wouldn&#39;t work with GORM as it used the name of the struct provided to select the table to look in.</p> <p>This is one of the examples in their Readme:</p> <pre><code>// Get the first record db.First(&amp;user) //// SELECT * FROM users ORDER BY id LIMIT 1; </code></pre> <p>Do you know if this would be the case if I provided an interface{} or shall I go test?</p></pre>klaaax: <pre><p>Well i&#39;m pretty sure db.First takes an empty interface as argument , then do some type switches or reflection. So there is some runtime typechecking going on anyway. Because that&#39;s the only way it can work. Same for mgo and. That&#39;s the cost of a lack of generics to abstract datastructures. What should be solved at compile time has to be solved at runtime. </p></pre>danhardman: <pre><p>Okay brilliant. I&#39;ll change that all now!</p></pre>kortemy: <pre><p>No, you should never &#34;provide&#34; an interface, meaning you never pass <code>interface{}</code> to a function. You should receive an <code>interface{}</code> in you repo, but when you call it, you pass a <code>&amp;User{}</code>. I can make it more clear with more words. :D</p></pre>kortemy: <pre><p>First off, I would recommend to keep naming clear - in your case <code>User</code> is both the model and repository(DAO).</p> <p>Second, ask yourself do you really need separate DAO for each model? Some say you do, some say you don&#39;t. :) I have talked with many Gophers on the exact same issue, and I recognized 3 approaches:</p> <ul> <li><p>No DAO whatsoever - meaning you have gorm.DB exposed to your http handlers, and each of them is accessing the db freely, depending on what those handlers do. This is most pragmatic way of doing things, because you have 0 bolierplate. In your example of <code>ReadAll</code> - why do you need a separate package and a function for that if its only 2 lines of code? You can call it directly.</p></li> <li><p>No DAOs, but a single DB access package - similar to above, you don&#39;t have model specific DAOs, but you are restricting access to db by writing generic functions like Read, Create, Delete, etc. Those functions would take everything as arguments - table name, query struct, and pointer to a results struct. You will read from query to dynamically create a statement, and write it to a supplied result struct.</p></li> <li><p>DAO for every model - what you were doing. It is good because it is ORM compliant. But as you mentioned there is repetition of common operations. And you cannot alleviate it completely because there are no generics in Go. You can bypass some of that by introducing something like CommonDAO, a repo embedded in other DAOs, and call through to it from other DAOs. This may seem like not much, but if your common functions are complex, then its worth it.</p></li> </ul> <p>Verdict? I like 2nd approach the best. Because it is providing me with some safety by restricting direct access to the db, but still enough freedom for a wide array of operations. Also, I don&#39;t like ORMs, but that is a different topic. :) If you are open to being convinced into using something else, check out mgo, driver for MongoDB. It is the most elegant piece of Go code I have seen and used so far.</p></pre>danhardman: <pre><p>I think I&#39;m gonna go with the 3rd approach and do as you and <a href="/u/klaaax" rel="nofollow">/u/klaaax</a> have said about having a common DAO which would be the base.go I think. I&#39;d just add the common functions onto that and any model specific functions would go on the model&#39;s DAO itself.</p></pre>

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

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