神刀安全网

Go is the new Node

I’ve started to think of Node.js is the Donald Trump presidential candidacy of programming. It’s gone from an amusing concept not to be taken seriously, to kind of concerning due to the seriousness with which others took it, to a real problem .

I should say that I actually personally like Javascript quite a bit. It’s missing some key features that are the subject of common complaints, but the language itself is pretty easy to write and understand, and literally the only acceptable tool for manipulating front-end elements in a browser.

But that’s just it — Javascript was built for and meant to be a language for front-end developers and designers to have a browser retrieve data, and manipulate its presentation on the screen. I’m fully prepared to admit I just haven’t been paying attention to the right crowd, but I almost never see anyone online making the argument that Node is a golden hammer run amok.

That said, most people have graduated from Rails/Django to Node, so where do they go next? I certainly don’t want to see orgs using Rails as long as they can avoid it, and moving backwards in any case is probably a bad idea.

Which is why I suggest: Go.

Now, full disclosure, I’m a total Go fanboy. I’m the guy in the office who responds to any mention of the possibility of rewrite with a helpful reminder that Go exists and is awesome. I’m a firm believer in the potential that the Go programming language has to offer every developer and company. In my personal life, it’s my go-to (heh) language.

But I feel fairly confident that my enthusiasm for Go is based solely in my experience in writing software with it. I’ve written lots of little utilities that I thought would be interesting to have in it, and I can say that I’ve been impressed with it almost every time. I’m not clinging to some arbitrary superficial quality of the language, but rather attached to my positive experiences writing Go code.

As an exercise in demonstrating the capacity for Go to replace Node.js, I decided to take the Express generator ‘s default app and write it in Go.

Let’s do this

If you’ve never used the Express generator, it’s a very simple affair. You install it, run express whatever and a new folder named whatever is created, with some pre-defined routes and Jade templates. Nothing crazy complicated, but it does present some interesting things to learn. The Jade templates included with the Express generated app support inheritance (which is the only rad thing about Jade, for the record), and Express’s route handling is obviously very good.

Apparently Jade is now Pug . I’m going to call it Jade anyway because this is a new development to me, and probably also to you.

To recreate it in Golang, I first replicated the folder structure, which meant basically copying the entire project, with some minor modifications. Obviously since this is Go, we won’t need the node-modules folder (which, according to Explorer, contains 971 Files in 205 Folders , dang!). We also won’t need a package.json or the bin folder (though I guess you could put that there, I’m cool with it either way.

For our templates, I opted to compile our Jade templates to regular HTML because while things like this exist, I am of the very firm and educated opinion that Jade is horrible garbage that I would eradicate in my first 100 days in office as president of Javascript.

Obviously you’re going to have to do some templating stuff to define where your blocks go and what your inheritance structure is. There is an official Golang post about the html/template package that you’re welcome to check out, but I found this post to be more immediately helpful.

The long and short of it is that our generated Jade template, which looks like this:

// layout.jade doctype html   html     head     title= title     link(rel='stylesheet', href='/stylesheets/style.css')   body     block content  // index.jade extends layout  block content     h1= title   p Welcome to #{title} 

ends up like this:

<!-- layout.html -->   {{define "layout"}} <!DOCTYPE html>   <html>     <head>     <title></title>     <link rel="stylesheet" href="/stylesheets/style.css">   </head>   <body>     {{ template "content" . }}   </body> </html>   {{ end }}  <!-- index.html -->   {{ define "content" }} <h1>{{.Title}}</h1>   <p>Welcome to {{.Title}}</p>   {{ end }} 

Note that there is an error template that is very similar to our index template, so I haven’t included it here, but it is in the Github repository .

In my first attempt at this, I had a very basic thing using only the standard library, shown below:

package main  import (       "fmt"     "html/template"     "log"     "net/http" )  var templates map[string]*template.Template  func init() {       // big thanks to https://elithrar.github.io/article/approximating-html-template-inheritance/     templates = make(map[string]*template.Template)     homeTemp := template.Must(template.ParseFiles("views/index.html", "views/layout.html"))     templates["index"] = homeTemp     errorTemp := template.Must(template.ParseFiles("views/error.html", "views/layout.html"))     templates["error"] = errorTemp }  type page struct {       Title string }  type errorPage struct {       Message string     Error   pageError }  type pageError struct {       Status int     Stack  string }  func homepage(w http.ResponseWriter, r *http.Request) {       p := &page{         Title: "Express",     }     err := templates["index"].ExecuteTemplate(w, "layout", p)     if err != nil {         log.Fatal(err)     } }  func notFoundError(w http.ResponseWriter, r *http.Request) {       ep := &errorPage{         Message: "Not Found",         Error: pageError{             Status: 404,             Stack:  fmt.Sprintf("Error, url not found: %v", r.URL),         },     }     templates["error"].ExecuteTemplate(w, "layout", ep) }  func main() {       dir := http.Dir("./public/stylesheets/")     cssHandler := http.StripPrefix("/stylesheets/", http.FileServer(dir))     http.Handle("/stylesheets/", cssHandler)      http.HandleFunc("/", homepage)     http.HandleFunc("/error", notFoundError)     http.ListenAndServe(":3000", nil) } 

Note that we’re using zero dependencies. This was ultimately my goal, and it worked fine, but I felt conflicted simply posting this on here and dusting my hands off as I walked away. For one, there are some legitimate questions about the usefuleness of the error route. I had to specifically add the error route to the application, because due to how Go’s multiplexer handles routes , there’s no other way to encounter the error page. The express version has this chunk of code in it:

app.use(function(err, req, res, next) {     res.status(err.status || 500);   res.render('error', {     message: err.message,     error: err   }); }); 

Which essentially watches for any possible errors thrown by express and calls this particular function instead. Note that you’d encounter errors here ranging from routes not being defined (your 404 errors), to things like your templates not properly rendering. With our above Go example, your templates failing to render will result in the app never starting at all!

panic: template: layout.html:1: unexpected bad character U+0022 '"' in define clause  goroutine 1 [running]:   panic(0x791300, 0xc082002f80)           C:/Go/src/runtime/panic.go:464 +0x3f4 html/template.Must(0x0, 0xe70028, 0xc082002f80, 0x0)           C:/Go/src/html/template/template.go:340 +0x52 main.init.1()           C:/Users/Jeffrey/go-example/first.go:15 +0xfe main.init()           C:/Users/Jeffrey/go-example/first.go:64 +0x56 

Another departure from the express app in this case is that our error route doesn’t have access to a stack. Obviously, there’s a place for it in our templates, but it’s likely we’d just use this to display actual errors instead of a stacktrace. Note that you could do something like:

func homepage(w http.ResponseWriter, r *http.Request) {       p := &page{         Title: "Express",     }     err := templates["index"].ExecuteTemplate(w, "layout", p)     if err != nil {         generalError(w, err)     } }  func generalError(w http.ResponseWriter, e error) {       ep := &errorPage{         Message: "Not Found",         Error: pageError{             Status: 404,             Stack:  e.Error(),         },     }     templates["error"].ExecuteTemplate(w, "layout", ep) } 

Which is fine, I guess. Error handling is not my most major concern, to be honest. I’m much more bothered with the route pattern handling behavior that the standard ServeMux provides us. To put it short, if you have the following code:

http.HandleFunc("/", someFunc)   http.HandleFunc("/things", anotherFunc)   

and you hit the route /farts/ , which function handles your request? If you thought it would 404, you’d be wrong. It’s actually someFunc . If you hit /things/and/stuff/ , surely that would 404, right? Wrong again, anotherFunc handles your request then. You know, for reasons.

When I set out to do this, I had lofty goals of completely writing this app in less than 100 lines of Go. "Look at how little code you need!" I’d proclaim. I’d get to the top of Hacker News, Rob Pike would follow me on Twitter, my boss would give me a pay raise and I’d die a legend. But alas, the standard ServeMux has failed me. It’s time to call in the big guns: gorilla/mux

Mux rules, and I genuinely think it should just be part of Go already . With Mux, our demo really doesn’t change a whole lot:

package main  import (       "fmt"     "github.com/gorilla/mux"     "html/template"     "log"     "net/http" )  var templates map[string]*template.Template  func init() {       // big thanks to https://elithrar.github.io/article/approximating-html-template-inheritance/     templates = make(map[string]*template.Template)     homeTemp := template.Must(template.ParseFiles("views/index.html", "views/layout.html"))     templates["index"] = homeTemp     errorTemp := template.Must(template.ParseFiles("views/error.html", "views/layout.html"))     templates["error"] = errorTemp }  type page struct {       Title string }  type errorPage struct {       Message string     Error   pageError }  type pageError struct {       Status int     Stack  string }  func homepage(w http.ResponseWriter, r *http.Request) {       p := &page{         Title: "Express",     }     err := templates["index"].ExecuteTemplate(w, "layout", p)     if err != nil {         log.Fatal(err)     } }  func notFoundError(w http.ResponseWriter, r *http.Request) {       ep := &errorPage{         Message: "Not Found",         Error: pageError{             Status: 404,             Stack:  fmt.Sprintf("Error, url not found: %v", r.URL),         },     }     templates["error"].ExecuteTemplate(w, "layout", ep) }  func main() {       router := mux.NewRouter()     dir := http.Dir("./public/stylesheets/")     handler := http.StripPrefix("/stylesheets/", http.FileServer(dir))     http.Handle("/stylesheets/", handler)      router.HandleFunc("/", homepage)     router.NotFoundHandler = http.HandlerFunc(notFoundError)      http.Handle("/", router)     http.ListenAndServe(":3000", nil) } 

We get this handy router object, with the rad-as-heck NotFoundHandler field. With this, any route that isn’t explicitly statisfied by our route definitions gets handled by our 404 function. All is right in the world. Mux has so many more features like route variables, regex things, method specification, all sorts of radness. To be fair, Express also has those things.

I’d include screenshots of what the Express app serves vs. what the Go version serves, but you’d just see two identical screenshots. So that begs the question, if there’s no difference, why use Go over Express?

This is a more nuanced discussion that really depends on your situation and end goals. The main argument in favor of Javascript is the sheer number of modules available on NPM. The body of work already done for you is nothing short of impressive, and no doubt makes it easier to get up and running.

I’d counter that argument with two bits of information. Firstly, Go has an absolute ton of really awesome libraries that take care of a ton of heavy lifting for you. Secondly, I’d remind you that Javascript didn’t always have this massive body of work. It got there by developers like you and I writing the libraries we needed. There’s even an opportunistic side to this argument that writing a(n idiomatic) Go port of a library you find to be essential to your every day life could mean very good things for your career and reputation as a developer/company.

Beyond all that, I’d argue that you want to choose a tool based on how much you can get done within that tool alone. People don’t mind that Javascript doesn’t come with unit testing features out of the box because there’s a ton of unit testing frameworks. I’d rather have that sort of core, important aspect of my workflow built into the language. There’s probably value in an entire other post about the awesomeness of unit testing in Go versus other languages, but that will have to come later.

I’m by no means the first person to advocate for replacing Node with Go, for the record. Just after I started drafting this post, Alexandra Grant from Digg posted this wonderful Medium article detailing her experience doing the same . Kelsey Falter gave this talk on the matter almost two years ago!

In conclusion, I like Javascript, and I really like Express, too. I just think Javascript is the wrong tool for backend web programming, and that Go is a much better tool. That said, I’m neither your boss nor your mother, so PLEASE build websites however you please.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Go is the new Node

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮