<p>Hi <a href="/r/golang">/r/golang</a>!</p>
<p>I have been using Go for a couple of years now to build various automation tools, and absolutely love the language. While I have created a number of smaller web-based projects in Go, they have all been contained in a single package and were very disorganized.</p>
<p>I have a bit of a larger web project planned and would like to ensure that I start of on the right path. Usually I would separate the UI and API and build a single page application, but this project I would like to use html/template and render the HTML server-side. </p>
<p>What I would like to know is how I would organize my code and how my handlers should be structured within sub-packages.</p>
<p>The second question I have is how things like database access should be handled.</p>
<p>To explain my thoughts, I have included two examples and would like to know what is the more "Go" way to do this.</p>
<p>If I am completely off, please let me know and possibly point me in the right direction.<br/>
<br/>
</p>
<p><strong>Method #1: Handler Struct</strong><br/>
<br/>
blog.go:</p>
<pre><code>package blog
import (
// Imports here
)
type Blog struct {
title string
author string
date string
}
type Blogs []blog
type BlogHandler struct {
db *sql.DB
}
func (b *BlogHandler) getBlogs() Blogs {
// Code here
}
func (b *BlogHandler) DisplayBlogs(w http.ResponseWriter, r *http.Request) {
blogs := b.getBlogs()
// Load template and insert blogs
}
</code></pre>
<p>main.go:</p>
<pre><code>package main
import (
"blog"
// Other imports here
)
func main() {
// Code creating database here
bh := &BlogHandler{db}
http.HandleFunc("/blog", bh.DisplayBlogs)
http.ListenAndServe(":8080", nil)
}
</code></pre>
<p> <br/>
</p>
<p><strong>Method #2: Handler function in package</strong>
<br/>
blog.go:</p>
<pre><code>package blog
import (
// Imports here
)
type Blog struct {
title string
author string
date string
}
type Blogs []blog
func getBlogs(db *sql.DB) Blogs {
// Code here
}
func DisplayBlogs(db *sql.DB) {
return func(w http.ResponseWriter, r *http.Request) {
blogs := getBlogs(db)
// Load template and insert blogs
}
}
</code></pre>
<p>main.go:</p>
<p>package main</p>
<pre><code>import (
"blog"
)
func main() {
// Code creating database here
http.HandleFunc("/blog" blog.DisplayBlogs(db))
http.ListenAndServe(":8080", nil)
}
</code></pre>
<p>Thank you all for your help!</p>
<hr/>**评论:**<br/><br/>kerakk19: <pre><p>In my company we are using first way. </p>
<p>It allows you to easily pass additional data(config, session store, models). It's also very clear and easy to read.</p>
<p>You should split handlers and models(handlers shouldn't have db connection in them).</p>
<p>Handlers are for reading session, i18n, cookies and every other Web stuff, where models are doing things like db queries or data manipulation.</p></pre>thatgodude: <pre><p>Thanks! </p>
<p>I was going to separate models and handlers, but I'm unsure of what the best way is to do this.</p>
<p>Would I attach functions to the blog model (load, create, update, delete), or would I create helper functions for this? </p>
<p>How would I best pass the database to the models from the controller / handler structure? </p></pre>kerakk19: <pre><p>Well, database connection, config and other similar stuff should be read in main, where you are using them to create your model.
This model then you can pass to handler as a struct field and invoke functions inside handlers.</p>
<p>For example:</p>
<pre><code>type Model struct{
DB *sql.DB
}
AccountHandler struct {
AccountModel *Model
}
</code></pre>
<p>And in main:</p>
<pre><code>db, err := sql.Open(config.Database.Driver, config.Database.URL)
if err != nil {
log.Error(err)
return
}
accountModel := Model{
DB: db,
}
handler := Handler{
AccountModel: accountModel,
}
</code></pre>
<p>Now you have your model and handler type, where only thing needed now is to create methods to them.</p></pre>thatgodude: <pre><p>Hmm, this looks like it would become messy very quickly, if you define a model and handler for each section of the site. </p>
<p>In regards to the model, how would this work for a blog for example? Would the model have functions attached to return a single blog, all blog entries, delete a blog, etc? Would I basically be building an ORM here? </p></pre>kerakk19: <pre><p>Well, as far as we are creating our apps, code is pretty clean and understandable, even for new person in project.
For 12 handlers and few models main and code structure looks pretty good, but you're probably right for very, very big projects, and that's why we are looking for better approach to this problem..</p>
<p>Yes, model would have methods like SingleBlog(), AllBlogs(), which return blog to handler and handler is passing this JSON object to front-end.</p>
<p>Also for blogs, i think this would work pretty well. Handlers and models should be pretty straightforward, classic Add/Delete/Edit methods for posts/comments, maybe something more if you want your own photos and videos.</p>
<p>What do you mean by building ORM? If you're referring to struct types being same(or very similar) as database tables, then you're probably right. You could create few struct types like AddPost/DeletePost/EditPost or one bigger struct which contains all these 3(or more) types and use this to pass data around.</p></pre>thatgodude: <pre><p>I appreciate your suggestions. This is a much more complex project than a blog, so I wanted to make sure I understand the best practices for building out each component. </p>
<p>I'm still a bit weary of creating a model and handler for each component of the site though, it just seems like a lot of code duplication to me. </p></pre>kerakk19: <pre><p>Yes, there's some code duplication, but it's actually very useful, because you're instantly seeing all handler/model dependencies and it's pretty easy to add new things to single types(this model needs config? Here you go, but one code line more).</p>
<p>That's the place where you need to decide which thing is more important :)</p>
<p>Also it's pretty important to have middleware(s) to pass things like authorization/session/translations between handlers(we are using context for this) when you're taking this approach. This makes you to duplicate essential things only(max 2/3 lines for every handler).</p></pre>titpetric: <pre><p>I'm using both, and neither. I can't say "neither" is best practice, but it suits me well enough in some cases where individual dependencies can grow beyond manageable limits. For example, with a pool of something like 20 connections to various databases with their own credentials, it serves me better to provide a single database factory than to figure out how I'm going to create and name the objects and pass them as arguments or properties. The database factory provides me with the resulting sql.DB based on a string (connection name).</p>
<p>Think of it like holding a map[string]*sql.DB, and passing that to the functions. Except it's a package with a specific interface: <code>factory.Database</code> + <code>Get(name string)</code>. Adding support for new connections is then just the question of adding a few lines to a config file. Again, it depends on the scope/fragmentation of requirements, but if we followed best practices in every possible instance, I'd be working with about 300-400 different database connections with their own credentials for each available microservice that we run. Cutting this down to the major ones and not resorting to a global access everything database user gives us some trade off between practicality and security.</p>
<p>GL :)</p>
<p>Edit: I've had issues with the same line of thought before. So I created a bit of a research of possible ways to "pass stuff in" in a series of blog posts: <a href="https://scene-si.org/2016/08/01/golang-tips-and-tricks-part-3/" rel="nofollow">https://scene-si.org/2016/08/01/golang-tips-and-tricks-part-3/</a> - I'm just gonna link this one, I think at the bottom I link every article in the series. For educational purposes only :)</p></pre>qu33ksilver: <pre><p>I have found this blog post to be very helpful - <a href="http://www.alexedwards.net/blog/organising-database-access" rel="nofollow">http://www.alexedwards.net/blog/organising-database-access</a>.</p>
<p>I use the 3rd method in that. And have found it very useful on writing unit tests.</p></pre>edgenuts: <pre><p>Have you looked at <a href="http://gobuffalo.io/docs/getting-started" rel="nofollow">Go Buffalo</a>?</p></pre>thatgodude: <pre><p>Thanks for the suggestion :) </p>
<p>I feel like Go has all the tools I need in the standard library, and seeing as I want to learn best practices, I'd rather stick with that as much as possible.</p></pre>edgenuts: <pre><p>Fair enough. Sorry my suggestion wasn't helpful!</p></pre>dandeee: <pre><p>What do you think about something like this for a starting baseline?
<a href="https://github.com/dandee/HomeStash" rel="nofollow">https://github.com/dandee/HomeStash</a></p></pre>caseynashvegas: <pre><p>This is a great question, I recommend reading this article on applying clean architecture in Go <a href="http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/" rel="nofollow">http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/</a>. </p></pre>caseynashvegas: <pre><p>However this brings yet another organizational question. Do you have a packages broken up on technical boundaries or do you have packages broken on functional distinctions. For example do you have a "domain" and "usecases" package or do you have a "user" package with sub-packages? If you go with the latter approach what lives at the user package level? </p></pre>tiberiousr: <pre><p>I would recommend writing the client side ui in something like Vue.js or reactjs. Then just have your go backend return JSON data that the frontend can use. It makes the backend simpler and faster and allows you to take advantage of the fairly rich ecosystem of libraries and tools available for ui development. </p></pre>thatgodude: <pre><p>Usually I'd write a JSON API and use angularjs or react to build the front-end. Unfortunately I won't be able to do this for this project. </p></pre>tmornini: <pre><p>I'm curious.</p>
<p>Why are you not able for this project?</p></pre>thatgodude: <pre><p>This particular project requires dynamic content without Javascript. It's unfortunate, but not up to me. </p></pre>tmornini: <pre><p>Thanks!</p></pre>brokenprogram: <pre><p>The "right" way would be to use the x/net/html package instead of templates and perhaps when wasm becomes a thing with DOM access you can reuse the code and even provide interactivity (through the DOM/web APIs)</p></pre>comrade-jim: <pre><p>I suggest working through this tutorial:</p>
<p><a href="https://golang.org/doc/articles/wiki/" rel="nofollow">https://golang.org/doc/articles/wiki/</a></p></pre>thatgodude: <pre><p>While I love the Go wiki and documentation, unfortunately this doesn't go into details on the question I have. </p></pre>pinpinbo: <pre><p>At work, we went with the third options. All global resources such as database connection pool are being passed to request handlers via <code>context.Context</code>.</p>
<p>The request handler will then initialize model struct by passing in the database connection pool into <code>model.New(pool)</code>.</p></pre>thatgodude: <pre><p>Hmm, documentation from context pretty much states not to use context for databases (application scoped). It's indented use is for request scoped data. </p></pre>ale--: <pre><p>I'd suggest to take a look at gokit - <a href="http://gokit.io/" rel="nofollow">http://gokit.io/</a></p></pre>govision: <pre><p>Actually this one is really nice.
<a href="https://go-macaron.com" rel="nofollow">https://go-macaron.com</a></p>
<p>But if you want custom then google away...</p></pre>thatgodude: <pre><p>Thanks for the suggestion, but as I said, I'm not really looking for a framework :) </p>
<p>I have googled my questions extensively but unfortunately there is a lot of conflicting information out there. Worse, concepts are usually explained with a super simple webapp that fulfills one purpose (a todo list for example), and is not organized in packages. </p></pre>msubbums: <pre><p>Try Iris golang FW. It includes MVC kind of FW.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
0 回复
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传