The best Go framework: no framework?
threedots.tech
threedots.tech
We are more people, we are more experienced and have a decent engineering culture. Yet we're definitely less productive than the last average, traditional Java Spring Enterprise team I've been in. "Circumventing" a framework restriction (which rarely happens if you stick to good practices) is MUCH less effort than building things yourself from scratch.
gRPC and protobuf are just transport and serialization, they have nothing to do with business logic, on the other hand Spring is a heavy, bloated framework.
Most Java frameworks are complicated backed by layers of abstraction and black magic.
btw no framework does not mean you don't use any library, there are some good lib aka micro framework that have everything you need to build modern and decent api servers.
I hear that silly argument "no framework === rewrite everything from scratch" far too often.
There's a giant difference between libraries and frameworks! It's terminology: it's a framework if you build your app inside it. It's a library if you build it inside your app.
The later is fine. The former: I dislike it - even in JavaScript, Ruby, Rust or anything, I wrote a longer post on that[1], which got a lot of discussion on HN. Most of it too in the line of "lol, I use a framework because I don't want to write it all myself", completely missing the crucial first paragraph in which I carefully tried to explain the difference and explain that re-using code != using a framework.
> You may feel that building your services without a framework will take ages. Especially if you are coming from other programming languages. I understand that. I had the same feeling a couple of years ago when I started writing in Go. It was an unjustified fear. Not using a framework doesn’t mean that you will need to build everything yourself. There are many proven libraries that provide the functionality you need.
This is not true as blanket statement. It may. But with "a framework" you are bound by the architecture, upgrades, use-cases and so on that this framework covers. And limited by the ones it doesn't.
In practice, choosing a framework on day one of the project, means you cement yourself in architectural choices when you still lack all information about what architectures will be needed. You don't know your problem space.
All you know, for certain, is that his problem space will turn out different than what you thought it would be today. Flexibility to move along as this evolges is critical to "maintainability".
In practice, therefore, you'll quite likely end up with a framework that is severely harming your ability to write maintainable and congruent code over time.
Often times you can get away with a subset of functionality which makes for a simple implementation.
It’s not convincing to just call everyone stupid. Everything is a trade off. You have no idea what those could be in any particular situation so that’s why your whole argument falls apart
Then you focus on construction and coding and ignore everything else a framework offers, which has downstream effects on coding and construction. It’s not all about the code. The coding is actually the easy part
The only projects ppl pay us is for the ones that are so large and complicated that if you don’t use frameworks, you’re going to either die or go crazy
I asked him what is a better way to quickly/easily stand up microservices/apps for clients (90% of my job), if not Django (or Flask/FastAPI, etc).
His response was "django is great, but it could have been written better and in another language" ... well, sure, great... but that doesn't solve my problem, lol.
We use Django for tons of client projects and rarely ever hit the "limitations" wall. It is mostly CRUD apps, so that helps but I would argue the time saved using frameworks in our shop is much more than the time lost spent on working around limitations. I do think the author has good ideas tho, good principals... but if you know your craft well, you know what will/won't work in a framework and how to solve that.
There’s always a better way to do things but we live in a fallen world. Some bullshit will always arise to make your life more complicated.
It’s like you said, when you hit whatever limitation, you’ll have to figure out a way around it.
I wish it were less acceptable to play the "balanced" person in the middle.
If and when there is a balance point, someplace where the actual truth tends to be, imho, it's almost never in the middle.
There are reasons those frameworks (and Golang, too) tend to do things in an opinionated way.
I wish there were more strong opinions lightly/loosely/weakly/gently/another-word-ly held[1]. I think opinions too strongly held is a surer path to there than starting from a place of trying to "meet in the middle."
[1] DDG-ing the term showed a bunch of adverbs of holding! Here are a some:
why it works: https://www.nwea.org/blog/2022/strong-opinions-loosely-held-demystifying-social-emotional-learning/
someone also said something on medium: https://medium.com/@ameet/strong-opinions-weakly-held-a-framework-for-thinking-6530d417e364
contrarians take a stand, too: https://commoncog.com/strong-opinions-weakly-held-is-bad/I’m not trying to meet anyone in the middle. I’m saying use a framework all the time, every time.
> there will be a lot of projects where Django is a very poor choice.
often and lot is crucial - it's opposed to all. Because it implies exactly what you then continue to state: that its our job to figure out if this project is one of those "lot [..] with a poor fit" or one of the ones where it actually, and will remain, a good fit.
But when I say premise is weak, it’s a technical term. Doesn’t mean it’s wrong.
I mean, in this case I do think that, but that’s why you’re getting so much pushback. Even if the premise was right, you’d still be getting pushback.
On the next post, remember to spend extra time there. It always comes back to haunt you.
Remember that the reader is smart but in a rush. The less words the better. Keep it country simple.
This was my reaction. Every Spring app I've been involved with was a nightmare of gratuitous complexity that was nearly impossible to debug. I'm sure if I were a master of Spring I could figure it out, but that's the nice thing about Go--pretty much any programmer could work out what's happening even if they aren't particularly familiar with Go. You don't have to trace something from XML to Java, and there is basically no magic (maybe the odd bit of Go reflection is the exception to the rule, but it's much less common than in Java/Spring and the "magic" is much less magical).
I believe one thing that really tells the Java world apart from others is the heavy reliance on DI containers like spring/karaf/osgi etc. Once you understand that, everything is simpler.
I would say "believe me" in a face to face conversation, but even then it's proven useless :)
Working with Spring for me has become one of those things like when people suggest to "choose a boring technology" to build something. Yes, that's it. In the positive sense, of course.
There is literally an easy integration with everything you need, the learning curve is relatively smooth, and yes while it's true there are quite a few annotations you need to get used to, I believe after a few days you finally get used to it, and finally it simplifies a lot your development experience rather than making it worse.
For me the only reasons I would pick Go is because of native binaries (smaller footprint, memory, cpu etc), and it's "slim" for simple programs (like Python, but again binaries/native). I also like a lot Go's syntax so that's another pro.
Finally they are both very solid languages with strong tooling and wide communities.
Ps: the cool "new" guy seems to be Quarkus, though :)
For example, the code below is a complete Spring Boot application with all of the default configuration in place. It will take just a couple of minutes to have this running and it provides quite a lot of features under the hood - which you don't need to worry about.
@SpringBootApplication @RestController public class DemoApplication {
@GetMapping("/helloworld") public String hello() { return "Hello World!"; } }
- when things go wrong
- onboarding newer / more junior devs
For point 1, there are so many layers of abstraction and 20 page stack traces that you could fill an entire log buffer with just one NPE...
Kidding aside, I can't tell you how many times I've wrestled with the auto-configure magic. The reality is you'll include so many "starters" in a medium use app, you won't know whose including what. A polluted Spring container is a real problem. That isn't the only problem, but it's one of the more prominent. You may say "well write cleaner code!" and I would reply that Spring is conducive to writing code that doesn't fit well with the framework, and that's mostly because you have to understand 10+ years of architecture decisions when you want to do anything beyond the basics (That's why we mostly don't reach for the "Spring" way to do things anymore, just the simplest way). All that is to say, there is a reason Spring development has been supported by Spring consultants.
For point two, It's very easy get started but it's very difficult to mature into a fully productive dev. The things juniors and mids struggle with the most is unpacking autowiring and how to resolve those issues, how to properly handle async behavior (especially with Spring fully embracing WebClient and Reactor now), and database connections.
But yeah, outside of all of that, very easy to use, sure.
Because everything in Go is explicit you can actually follow a short series of function calls when debugging instead of staring at a 50-function stack trace with a bunch of obscure Aspect4J magic while you feverishly search the Spring JavaDocs for whatever specific error message you're getting.
Go is a dumb language and I like it. Everything's obvious and you can't get too cute. Now that there are generics the only thing that I'm really jonesing for is a decent collections framework in the standard library... generic map, filter, fold, etc. on slices would be a real boon to productivity IMHO.
My contention is Spring is great for those who have been initiated into it's ways and like to be the members of the Martian priesthood that sing canticles to the Omnissiah, but for those who want to understand what is going on and why Spring is anathema.
It's usually search "baeldung spring <whatever I am trying to figure out>" and I get a nice article telling me how to do it but also explaining it as well so I can have the knowledge in the future.
I think the main author Eugen (there are many now) has a nice book.
Uh no, you forget the part where you have to add a bunch of stuff to your build system (maven or gradle, usually), so it actually knows what to do with this.
You can't just compile that class with java -c, run it, and have a running application the way you could do it with lighter-weight frameworks.
I consider the boilerplate needed to build a main.go file that starts a gRPC listener to be harder.
The irony is that my team shifted from using spring boot to another framework for 'performance reasons'. Each instance only handles ~ 1k tps. Sighs
“I’m less productive in Go compared to Java Spring” => “the whole world is less productive in Go compared to Java Spring”.
I had to: manually manage sessions and jwt token Implement message flashing
Even reading data from a form was a little too verbose for my liking
I think our industry would look a lot different if more companies were keeping their Java apps up to date.
A framework is an added friction to picking up a development environment but potentially a huge asset to velocity once people are up to speed. You haven't gotten properly up to speed in that framework and that site's use of the framework. Perhaps you never spend enough time in that domain to really need to pick it up, in which case you'll always pay the "WTF is this?" tax.
But it's equally likely that once a person's up to speed in the framework they're efficiency is greatly enhanced.
A programming language isn't just the syntax of the language; there's also knowing the common idioms of the language, understanding the runtime / build environment, knowing when and how to leverage the standard and extended libraries/package ecosystem, etc. Frameworks fall into the "extended package ecosystem" which is vastly different from language to language, and sometimes _within_ a language if the language is sufficiently mature.
Some of it is "just math" and some of it is understanding the culture.
I agree Spring Boot brings a lot to the table (Spring Security alone, for example), but at the cost of significant downsides.
I see the benefit for larger and more diverse teams, but personally I would choose lighter-weight frameworks and libraries, even if at the cost of having to do more plumbing myself.
I have found Spring Boot to be a step forwards from the old Java EE world in basically every way except for this. Transactional boundaries are somehow more difficult to analyze.
This perfectly explains my problem with the fx library, and why so many java devs seem to gravitate to it
I've been working in Spring framework for years, and don't understand it. I suspect most of the people who develop it don't understand it either, as it's so enormous it's probably impossible for any one human being to really understand it.
I don't understand speculative execution or any of the other deep voodoo that happens in modern CPUs that spend enormous amounts of energy presenting the "I'm a PDP-11/70 with way more memory and bigger registers" lie to programmers.
These days I'm not even sure which part of my computer is running the networking stack, or where my computer is located.
We stand on towers of abstractions, and complain about the ones we're forced to notice. I don't think many people understand "it" these days.
Spring boot does put a layer of abstraction over ordinary “linear” code, but it is not black magic, if you understand its DI part, you will basically understand everything (in short, spring is only allowed to generate new code, not modify existing one. So it works its whole “magic” by generating subclasses at runtime. With this requirement in place, most of its working makes clear sense. It has an internal list of available beans, and you can inject them by type only (@Autowired/@Inject) when it is the only bean of said type, or you may have to be more specific by naming the desired bean. And it is a simple graph of such injects.)
And it is a well-understood convention, above all. I can go to another spring shop and expect to see JPA entities annotated the way I’m used to (which is another very powerful feature. Can it be misused? Sure. Does it save a shitton of time, and less code means less bug? Yep. Plus, before the “ORMs considered harmful” people join us, they were always meant for OLTP, not OLAP, and as a mapping to/from db records, where they absolutely shine. Don’t try to OUTER JOIN on a subquery of whatever with them, but feel free to write your SQL and map its results to a business object).
You don't get type safety for your database queries without jumping through hoops (compare anything it provides to LINQ — it's not even in the same league). Hibernate feels like a significant step-down after Entity Framework (I might be an idiot who doesn't know how to use it, that's entirely possible, but this just proves the point — it was easy to get started with EF, it didn't require a doctoral in ORM studies to become proficient with, it's dead easy to write queries for (thanks to LINQ), and the vast majority of those translate to pretty efficient SQL which is fine for 99% of my queries).
You get lots of behavior decided at runtime (while ASP.NET for the most part is very explicit in its configuration).
It also doesn't use many annotations (most of those I use are fully optional, like adding pretty names to database columns; and those can be configured through explicit method calls).
It does require DI (and IoC containers are swappable if you don't like the default), but I didn't have nearly as many issues with it as when working with Spring projects, whatever the reason may be.
I mean the use cases of that will be limited these days; application architectures have changed to the point where what used to be a module within a Spring application can now be a microservice to be enabled or spun up depending on rules defined outside of the application.
But this is a very superficial point of view, I haven't seriously touched Spring for a decade and, thanks to some experience with Go, I feel like I won't need to either for most modern-day applications.
While Go may not be for everyone or for every application, its mindset of keeping things simple, just use the SDK or some libraries, etc does make for better developers. IMO.
I think it's fine for small teams not to use frameworks, and maybe 200 person engineering orgs are made up of many small teams who just want to agree on a protocol rather than shared framework/platform but once the scale starts to increase you really need to start tightening up and implementing some better standards.
People constantly talk about not being Google, well let me tell you the people scale is all the same, the amount of legacy infra is all the same. If you haven't peaked into the depths of the messy multi-decade enterprise you have no clue. Your 4 year old company that you joined 6 months ago is no comparison to something with a legacy of 3 decades with 2k+ engineeers scattered across a disparate org trying to modernise in the cloud or whatever comes next.
Full disclosure: I work on https://micro.dev - a framework for Go
No-framework Go makes the most sense to me, because a big framework doesn’t make sense for all types of project, and the set of projects where I’d reach for a big framework just doesn’t intersect _at all_ with the set of projects where I’d reach for Go.
When I'm working on personal projects, frameworks don't make sense for me. When I'm trying to engineer something at scale e.g https://m3o.com then I need that standardisation at the platform layer, the framework layer, the API layer.
What I'm saying is that I find business logic-centric API services to be a good fit for larger frameworks that take ownership of more of the low level logic, and I don't find Go to be a good fit for those, at all. Inversely, I find Go to be a much better fit for the more "technical" services (provided we're not at the must-squeeze-every-ounce-of-performance C++/Rust level of requirements), but I also don't want a framework getting in the way.
For our small-time ops use we ended up on Gin/Gorm/Zap/Opentelemetry + some code for internal SSO and it works fine but I'm slowly thinking of making a wrapper that preconfigures that setup as there is pretty much same boilerplate repeated between many projects.
In my experience the problem with this kind of conversation is that you can actually have a pleasant experience in all permutations of the above. Since people have a ceiling on the number of projects they have worked on for a long enough time to have a reasonable opinion on the approach, they come out of this with massive biases and cannot accept that others have had pleasant experiences on the same tools and team sizes if they have had an unpleasant one.
I _promise_ you that you can absolutely have a relatively long-standing codebase without a framework that is a dream to work on and has been touched by many engineers in a huge corporate setting. Similarly I could show you absolutely unworkable travesties that have to eventually be rewritten despite being written with frameworks that claim to prevent this kind of thing occurring.
Conversely, you can absolutely have a “relatively long-standing codebase” with a framework “that is a dream to work on and has been touched by many engineers in a huge corporate setting.” And I could show you unworkable travesties that have to be eventually rewritten despite being written in library-only microservices that claim (because “micro”) to prevent this kind of thing from occurring.
Whether selection bias from your experience or an appeal to authority, there isn’t much to your argument. To me it just sounds like comparing well managed to poorly managed codebases.
If I have to manage a team, I’d much rather have the framework laying the groundwork for documenting “how we do things” than rolling consistency out ourselves.
Ah, so an organization so bloated that you have entire teams "managing platform tooling" (whatever that means) instead of iterating on features customers actually care about.
I don't miss the days where I had to deal with random missing or conflicting beans stopping my app from starting. The nice things about a Go app is because the control flow is so exposed, if it compiles you can be pretty sure it is going to run, and if it doesn't compile you can go to the red in your IDE and fix it.
Adding an endpoint is a matter of updating our OpenAPI file and doing `make oapi-gen` for the code generator we use. I think this is a similar level of effort to doing the same with a mvn or gradle command.
The real difference I notice between Go and Spring microservices though is how much easier it is to dev and debug multiple microservices - it is trivial to compile all of our Go microservices and boot some of them up for debugging without eating all of my laptop's RAM, which is especially relevant if I also want to run them in Kubernetes locally.
I will say I do miss some of the niceties of aspect oriented programming with Spring.
And then what we are left with is a huge, old project vs some new microservice, which would be an unfair comparison in any language
Understanding how the framework wires things together is important.
If you look at the problem more holistically (say "run a reliable service customers want to use") then that metric is just one of many, and more often than not counterproductive vs some other metrics like maintainability and resilience for instance. IME frameworks reflect that and only let you write code fast, or get the hello-world-demo out fast, but neglect the later stages.
I don't know if the conclusion is no framework is better overall, but the frameworks i worked with at least showed mixed results overall.
For instance, when Oauth became popular I was maintaining several apps where the original authors had never dreamed of an alternative login method (email and password had been the norm for longer than any of us had worked as developers).
The Rails apps required a few small config changes which I could get from the documentation then HTML for the "Login with Facebook" button.
The framework-less apps were disasters. Each had their own way of doing things. Some used libraries that were no longer maintained, others had rolled their own authentication workflow or half copied the code from another project.
There is a lot to be said for frameworks when facing an unpredictable future. There is a cost to be paid up front as far as learning the framework but the benefits on the back end are enormous.
What Rails configuration would be pertinent to Oauth? Perhaps you mean the application was already using some kind of third-party authentication library (e.g. Devise) that supported Oauth? If that's the case, didn't you just luck out that the developers happened to choose a library that 1. Was still being maintained. 2. Had already added Oauth support?
Most of the Rails apps I have encountered in my career (especially those predating Oauth popularity) used hand-rolled authentication, each different to the next, so it seems that you could have just as easily fell into the same trap you saw elsewhere. It is not clear how Rails saved you here.
Most of the useful stuff in Spring is just thin wrappers over other libraries (including the standard library) anyway.
Those things were basically first implemented in Java. We could argue about subtle differences between Avro and Protobuf but I'm not aware of any credible competitor to gRPC on the JVM in the last 10 years. Thrift and Avro RPC libraries died out years ago, Akka clusters are rather exotic.
Spring is more like traditional application development, where you revisit your code multiple time during its long life cycle. In comparison, Golang is more like spit it out fast and forget. Its syntax is so dumb that you can’t possibly misread the code from any angle, so maintenance is also brain dead simple.
In this aspect, using frameworks in Golang simply defeats the point of using the language, because frameworks make codes harder to read. The language is to be used like C, where (almost) everything is manual and explicit.
That's very much a matter of opinion. In my opinion, making every manual and explicit makes it hard to read if you're trying to do anything vaguely high level, because you lose the forest for the trees.
So many hours wasted investigating fringe http2 characteristics causing odd bugs on the platform.
Outside of Go (I assume because it seems pretty popular there) the clients for other languages are so hit and miss.
I had to pipe a json into the python client to configure a grpc client (which seems bad) and it took me a while to dig up the correct incantation.
I like the "contract" that GRPC implies but I am not sure I would choose it again.
So much money was wasted writing and maintaining those frameworks that was so flabbergasting.
Java/SpringBoot/etc micro services developers have certain way of doing things and they are certainly going to hit lot of problems with Go. Since I have worked in typical Java/J2EE/Microservices project all my career, I think, for most devs coming from larger Java ecosystem, mapping their existing understanding to Go way of doing things is pretty much impossible.
It sounds like this would lead to more reinventing the wheel than I'd like to see in a product like a web app/service.
It means literally nothing. A huge company making lots of money has no relation to the quality of its code, processes or tools.
That's not only true for non-tech companies, but even the heavily tech ones whose all products are software.
Assuming this means grpc-go, that's a framework. Perhaps your seemingly negative experience with it actually echoes that the best Go framework is no framework, contrary to your opening position?
If you don't like GRPC and Proto, use REST. `http.Client{}`, boom make a call.
Go is built for microsevices. It probably has the best std library for web services of any language.
I didn't need to read the rest of the comment after this.
Having an ecosystem of in-house libraries tailored to your products' use cases is not the same as developing an "ad-hoc framework". Where I work, we have several languages deployed in our fleet of microservices. A couple have frameworks, and these frameworks are "try to do it all" types, complete with dependency injection and layer upon layer of abstraction intended to help engineers avoid "needless" boilerplate or what have you. They're slow and difficult to debug/troubleshoot for any case that isn't anticipated by the frameworks, and even sometimes then. No, thank you.
Microservice architecture has its own problems, but these problems tend to be easier to reason about than the services themselves that use the frameworks.
Isn't this generally the advantage of using open source frameworks? The mature ones have run into most of these roadblocks, and have a solution for it already. And the ones with good taste have escape hatches to allow you to easily bypass for the one they haven't got a solution for. I've heard bad things about Spring, but I'd put forward Laravel as an example of a framework that can do an awful lot for you if you want it to, and gets out of you way when you don't want to use it for a particular task.
Now that I'm exploring Go, I'm surprised at how many books/learning materials are basically surveys of the features of the language, and then it's basically - "Start building!" "Introducing Go" is just 100 pages for example.
Thankfully, they have finally done away with this but it is sad that they ever allowed these horrible patterns in the first place. Another abysmal area is SignalR (clearly developed by children).
I still dislike what ASP.NET did with their startup, and I don't think it's improved much. The minimal startup is just a bunch of magic (where did the var 'args' come from? Just magic.)
Framework folks are people, they try new things, it doesn't always stick.
What is main(), where did it come from? Who calls it? What is argc and argv? Who puts that stuff there.
I suppose it's a quibble, but this annoys me. It shouldn't matter how many layers of abstraction there are, because I should only have to interact with the topmost one. For example when I write vanilla Java there are, off the top of my head, the following layers of abstraction: the Java language, the JVM, Userland, Kernel, C, X64, and CPU microcode. No abstraction is perfect and maybe the day will come where I need to understand the hardware frontend on x64 chips to get my Java program working right, but in the overwhelming majority of cases I only need to worry about the abstraction presented by the Java language itself and maybe on the operations side those of the JVM.
All automated computing abstractions are, in the pathological case, leaky, but there comes a level of leakage where it essentially stops being an abstraction at all, useful or otherwise.
Spring Boot isn't an abstraction, it's a language extension implemented via Java annotations. Thus you must in addition to understanding the relevant parts of Java, also understand the relevant parts of Java/Spring. Personally I think it's poorly documented and doesn't really have a cognitively manageable semantic theory, but it's been a long time since I've worked with it so maybe that's been sorted out?
Spring Boot isn’t an abstraction, but the language extensions and the things built on them within the frames are (badly engineered ones at that).
99% of cases people are better off using a boring conventional web framework and implementing their actual business logic on top of it.
In Go, not necessarily, because net/http actually is what many languages would call a "minimalist framework".
There's a sense in which pretty much everyone here is right. You do need a framework in the general sense; you can't expect to just throw someone a TCP socket and get reasonable work out of them because the web is very complicated nowadays. But the reason why you can "do without a framework" in Go is that it is already a minimal one of its own, and in particular, that "minimality" focuses on providing a mechanism for composing various bits of HTTP code together.
So, do you need CSRF protection? Go grab this: https://pkg.go.dev/github.com/gorilla/csrf It operates with the integrated net/http Go framework to provide CSRF protection. You are objectively not abandoning the ability to have community-written CSRF protection when you go "frameworkless" in Go. Gorilla in general provides lots of mix-and-match pieces: https://www.gorillatoolkit.org/ , like cookie signing: https://pkg.go.dev/github.com/gorilla/securecookie
I think calling Go "no framework" is kind of misleading, because I get what you're saying, but the standard library is not the "no framework" option. The "no framework" option is more like using https://github.com/valyala/fasthttp , which is its own server implementation with an incompatible API that does in fact remove you from the community code, other than the code that works only with that server.
Don't over-read my post. I am merely saying that Go essentially ships with a minimal framework and so it is misleading to say or think of it as being "frameworkless" (and that is a criticism of the original link, yes), not that it is mandatory to use only that "minimal framework" and all other things on top of it are a bad idea. There are times and places to want large, preconfigured packages of additional functionality. But if there are times and places where it is not desirable, Go does come with a minimalist option by default. It is a pretty good, not-very-opionated option suitable for a standard library, where development is slow and there's not much room for experimentation in what a "full" web framework should look like.
The standard library minimalist framework even makes the other frameworks pluggable. You can, for instance, write an API website, then realize that if you need a conventional HTML website, you can plug in a framework for that part, but leave the API part as-is, simply by routing the API URLs to the existing API code while routing the HTML website to the http.Handler provided by the framework. Even "not using an additional framework" is not the commitment it may be in other environments, because the net/http foundation is still there.
You can see the opposite in projects like Echo, Gin, Beego, etc., that eschew the standard library to various degrees and try to build the kitchen sink themselves. Sometimes this works! Echo is very popular, despite having nonstandard handlers and context. An absolute Go newbie is probably going to have an easier time using it than trying to pick out the best collection of libraries themselves.
I would love to see more 'blessed stack' collections that tie together good libraries such as this one: https://github.com/mikestefanello/pagoda
For other languages that is different though.
So learn a framework for whatever language you use, that's useful! But also learn to recognize when you are working against the framework and would be better served with a few libraries that you can combine in the way that you need. Frameworks optimize for certain use cases and expectations, and if your project is no longer aligned with those, it's probably a good idea to think about an exit strategy.
Definitely try something like Python + Django, Ruby + Rails, Java/Kotlin + Spring to get a feel for what a mature framework can really do.
Then if you find you still don't like frameworks and the style of programming associated with them then sure, find a home in one of the ecosystems that are more inclined to roll it your own way.
Frameworks certainly aren't for everyone but using bad frameworks and then pretending all frameworks are therefore bad is highly prevalent among more recent developers. Try the good ones and then make up your own mind.
Lack of a decent Rails/Django option for JS/TS is part of the issue IMO, since that ecosystem is so popular atm.
I also wonder if you have to get burned a few times rolling your own and that's just part of the journey :)
I was fairly new to "modern" web dev, starting a project, and went with Django because I had more Python experience than anything else. But I found templates very lackluster and poor to integrate with all the different JS ecosystem libraries people are creating to do very cool things on the frontend. Unfortunately, hooking React up to Django is a mess and most people doing this are still 5 years behind in React-land. Are you going to hybrid some template and some React? Are you going to containerize your django and react build system together?
Basically, I wanted a robust frontend ecosystem, well integrated to a simple backend (no Spring, Django-Rest-Framework monstrosities).
That led to React -> Next.js -> Typescript backend -> the rest of the niceties around it like Prisma (ORM-ish)
Create T3 App simplifies all the setup into a template and is extremely fast to start building features. No js build system setup, no python virtual envs. tRPC is an optional nicety.
I think Express specifically was fine and great.
Also when working with a team the value of having a lot of documentation, stackoverflow questions a google search away and an obvious way of doing things seems incredible valuable.
Does building your own microframework brings joy and a sense of accomplishment? yes it does. Does that correlate with accomplishing business needs. Not necessarily.
I mean, unless you want to remain a junior forever.
Going to have to also agree with the OP. Frameworks are great for the majority of real world cases. There are definitely cases where specific optimizations are required and maybe a framework is not best fit but those are rare.
Lets try to steel man your disagreement though. Having worked in larger orgs I believe there is a trap that some fall in when using frameworks. Ignoring what is happening inside of the plumbing of the framework and ignoring why the framework is doing it. From that perspective you can fall in the trap of not having mastery of domain and it can limit your career in the long run.
On the otherside my argument is that frameworks provide immense value to orgs of all sizes. Even for a single person team there is value because you are not wasting your time on problems that are not part of the business. You should understand how the framework works in the long run though. A lot of these patterns carry over between frameworks and languages, mastering your first one will open a lot of doors in the long run.
How much time?
Sometimes a task that takes X time and seem unrelated to your "main" task, actually increases your productivity when doing the main task.
A prime example is sleeping when trying to build muscle and lose weight.
On a surface level, sleep is a waste of time: you should be in the gym lifting weights or out there running/cycling/swimming. But actually no. Sleeping improves your results, and improves your performance on your workouts.
I'd argue there's a similar dynamic here.
Working on the lower levels of your problem might seem like a waste of time, but it's actually not. If nothing else, you feel more enjoyment from your work. Being engaged with what you do and not being engaged is huge.
Another problem that often occurs when you are always only working in the "high level" is you have no idea how to do anything well unless the framework does the heavy lifting for you. As soon as you step outside of what the framework was designed to help with, you are lost and have no idea how to approach the problem. All your approaches are half-assed solutions. You see this every single day on "big teams".
There's a time and place for framework/no framework, though I'd err on the side of not introducing a huge framework until it's actually needed.
Source: I work on a real, shipped for 3 years, SaaS product primarily written in Go (multiple services). The primary applications are framework-less, but some of our operators make use of kubebuilder (which certainly would qualify as a framework).
Frameworks are great (sometimes), but pay attention to who is giving you this advice.
No thanks. Give me ASP.NET Core, Django, Spring Boot for a development organization with more than 1 developer.
It makes it easy to get started on a project as there is no time i need to spend thinking about the tools im going to use, its just provided all of them for me, in mostly high quality, relatively easy to use implementations of most basic things.
The Go devs did in the past include external libraries in the stdlib, the most significant one being "golang.org/x/context" moving to "context". This was a very important inclusion because it meant the stdlib could start offering context-aware APIs, and it was a signal to the rest of the ecosystem that the context API is stable forever and other packages can make their APIs context-aware without risk of future breakage. With sqlx, none of these issues arise: the main compatibility interfaces between different SQL libraries (particularly to the drivers providing support for different RDBMS) are already present in the stdlib.
> Having such a large standard library to me smells like you can't build one as nice as that with the language.
means that you're implying that the Go stdlib is written in non-Go because Go would not be expressive enough. If so, that's a wrong assertion. The entire Go toolchain is Go, including the compiler, runtime and stdlib. If you happen to have Go installed, you can find the stdlib at /usr/lib/go/src.
OP could have written about the two different types of code reuse and why one lends itself better to Go: Libraries vs Frameworks.
Java has magical frameworks because we have a magical JVM and first class (as in baked into the language) metadata utilities at the language level. Java can define & load new objects at runtime. Loose coupling? My god, this is as loose as it gets. In Go, you'll need composition of processes to accomplish the same thing.
Code reuse at Go happens at the library and networked-but-loosely-coupled component levels. This covers a lot of territory but not things that are, in effect, 'containers of components'. If you are in component oriented code sharing territory, Go is the wrong language. With Go, a framework can't do all the magical stuff the other kids' FWs do, but you get most of the pain of using frameworks. It is a language issue.
in general: Partially realized abstractions place greater demands on the language and its runtime capabilities than do partially realized composition (e.g. apis).
I've been doing small projects with go for a while and while I enjoy doing it in go (I like the predictability that types bring to me and the fact that I do not need to go through the tomes of docs to figure out something) I still feel every single time that I'm reinnventing the wheel.
The task is simpler with SPAs since all you need from go is a json api, but if you want to build something complete with htmx you'll have to deal with many questions yourself. E.g.
- orm / db access
- Signup / login flow
- Emails
- Static assets versioning
- template helpers
- CSRF?
- Routing and reverse routing
- Page structures (you want to have the same field to look up page title, right?
- Partials for your templates (how do you do form validation in a nice and generic way)
There is a lot of stuff like this. None of this is impossible to solve, it's quite easy in most cases actually, but it adds up and as I said, you have to know how to do it write
One thing for sure is that if you implement the concepts yourself you don't need to abstract them away too much (or maybe you do if you want to reuse all the nice things in your next project). For me the most compelling thing is types and the fact that I'm not dealing with django/laravel magic with long stacktraces that are meaningless to me, but I still return to thinking about doing next project with rails from time to time, just because I've implemented form validation hundred times already.
One good point about frameworks is about limitations - they are there and this is something that's quite hard to overcome at times. Rails framework does things the certain way and if you want to do it differently you have to fight with the framwork or make an attempt to push things to upstream which I'm pretty sure is not the main goal of your project.
(edit: made list a list)
I do most of my Python work with PyCharm on a Windows machine, frequently with poetry as a dependency manager, but lately I was writing a script for burning DTS-audio CDs in a single step on my Linux server (has the optical drive) and made the decision to only use the Python standard library and do it all in a single file I edit with vim. The result is 150 lines of code that spin like a top.
I went through a phase of doing all the problems on HackerRank and learned to love "single-file Java" and discovered that you can do almost anything in a single file except declare a public class (if it is all in one package, default access is all you need.) It's not the usual way I do things but I like Java a lot more knowing I can write simple programs without maven, an IDE and all of that.
I don't enjoy working on enterprise codebase where there's thousands of files that do very little and the code is refactored so hard that you need to jump between 20 classes to understand how it all works and fits together.
Leetcode solutions are usually single file solutions.
My programming language and interpreter are more than one file though. A parser that lexes and creates an AST and an interpreter class and a class for each kind of AST node that do codegen.
I could probably hast used a single file and inner classes.
Sure your domains aren't so pure anymore because you've cut some layers of indirection and simplified things. But the cognitive load is so much smaller.
Such a pleasant delight to work on "dumb" code.
As always, it depends on the size of the project. But my threshold for keeping things simpler has certainly shifted towards trying to keep things simpler when possible.
It will be nice to one day factor it out into something I can import versus having to simply copy the template repo.
The stdlib is great but it doesn't give you sessions, orm, csrf, sass/minification, good templating, embedding, etc out of the box. You have to paste in lots of boilerplate to get those working.
https://git.eeqj.de/sneak/gohttpserver
It's WTFPL so feel free to cannibalize it in whole or part to save time. master is a bit outdated as I am moving it over to use uber/fx:
https://www.dotconferences.com/2014/10/blake-mizerany-three-...
Of course the only absolute truth is that there are no absolute truths, so no point of being dogmatic to the extreme.
Still, with experience I came to realise that dependencies carry their own weight, which has to be balanced with the weight of bespoke implementation (which can span the whole spectrum from "obviously" to "NIH")
This was all fun and learning, but wouldn't mind having a "Flask" or "Express" version of a Golang framework which took care of all the boilerplate out of the box.
We did try some existing solution to no avail like Tiny etc.
But ended up building a non-framework framework anyways especially to support new features/modules and new devs coming it.
Also what I like most about Django and Django Request Framework is that it saves me so much writing and reading in almost every case. Not sure how you're supposed to achieve that without something that looks like a framework or how writing ten times the amount of code is more sustainable and maintainable.
This is something that most even senior developers don’t grok. Default so called project starter scripts of the node world don’t help either.
The more structure you build before you know wth you’re doing, the more difficult you’ll make life for yourself down the line when you inevitably have to refactor code.
as long as the codebase not overly layered, I guess any framework is fine
only need to split to 3 kind of layer:
1. serialization/transport layer (codegenerated) -- framework goes here
2. business logic layer (one that unit tested), only input struct, transform/process, and output struct (DTOs)
3. persistence/3rd party layer (codegenerated too), add additional go source code file for things that wasteful to be codegenerated, only input struct, basic persistence methods or network calls, and output struct (DAOs) goes here
if using gRPC layer 1 already codegenerated, so only need to fill layer 2 with business logic and codegenerating layer 3.
Problem is, many MANY people do not have sufficient experience to know the perils of putting the framework in charge of the architecture, so they do just that, and decry any other approach as "Java-esque." (That label says an awful lot in and of itself.) The impact of this choice often doesn't become apparent until much farther down the road. Then you start getting into the weird cottage industry involving brittle hacks with DBs/frameworks in order to delay setting them up so that you can run unit tests in under ten minutes.
It's ridiculous.
Part of the blame lies with the devs: refusing to take responsibility for your architecture means you are at the whim of the framework. And the framework devs are also at fault for pushing the lie that you merely need to write the absolute bare minimum of code and everything else will be taken care of.
This is probably why I find almost every framework deeply unsatisfying. Gin, in Golang, doesn't bother me much for whatever reason, but it doesn't seem to aspire to the same number of things as other frameworks.
A more measured approach would be to avoid 3rd party packages that aren't very popular or are not particularly active.
Node's standard libraries (http, fs, etc) give you already enough to work with.
Sure, you're going to have to re-implement plenty of things (e.g. file serving, which anyway isn't great in node), and many other things but there's plenty of pros:
- you can always understand how things work and change them to suit your use cases. That's just not the case with external tools. Good luck figuring out the codebases or even be able to build them locally
- it's much better for educational purposes to understand how things fit together and are supposed to work
The biggest con: security. Libraries and frameworks likely patch many vulnerabilities you didn't even know existed. This alone pushes me off from this approach and instead I fall back to some bloated framework, but security is important.
The second biggest con: performance. You're likely gonna do plenty of performance mistakes if you don't have a good knowledge of how Node really works.
You wind up with a perpetual revolving door of immature implementations instead of incrementally improving something with known flaws.
If you select a framework and work in the same scope as the framework intends to, then you can onboard new engineers much more easily by telling them: We are working as the framework defines, look into the documentation and you will understand the basic architecture of our software.
Of cause, devs can maintain their own documentation, but I guess this usually is something that is not done very well.
My personal experience: As soon as a company started their software without a framework, it became a huge mess. While onboarding, I observed very bad architectural decisions and even very severe security issues.
I'd say, only go with no framework, if you are experienced enough to technically create an own (good) framework. From my observations, going without a framework was usually a decision done by young engineers.
All you need to do is add a logger of choice, which will be different based on your architecture (one / multiple servers) anyway.
You might want to add user auth, which is often an external server anyway.
Maybe you want some boilerplate / codegen tool to add/remove/select objects from a database. But I personally don't use that because it's hard to optimize performance with those tools and SQL is already a good tool for it imho, don't need an extra abstraction.
In summary, all you need is the standard lib and maybe 3 external libs in Go, because it was made in a time when an api was already pretty standard.
In other languages you need way more extra stuff to create an api, so the need for a framework is greater.
I use gin. It's not perfect, especially streaming (EventSource) is broken. But the rest has actual hand on solutions.
ctx.SecureJSON()
Not a single one of the other packages has something like that. Keyword: JSON hijacking protection. Yes it's still a thing.Since GitHub Copilot, I ditched SQL query builders as all the scanning and repetitive work is handled nearly immediately. I also ditched Chi and handle routing myself. Same for logging.
The issue is spending time doing things that took one line before with a framework (e.g., parsing URL path params). But it's actually satisfying.
So I can essentially create a map[string]string by splitting slashes and going 0 = key, 1 = value, 2 = key, 3 = value, etc.
If a handler uses a different structure like /resources/{resourceID}/{subresourceD}, I will have the func specifically handle that case.
Some ways may work better but this works for me and it's not magical, and I tend to touch that code once and never again so it's then out of the way, all in a file.
And this is the root of most problems described by author. If I start project on Python - I know that I can take Django and use it for at least 4 next years, spending one sprint a year tops to migrate to next LTS version. In Go only standard library currently gives me that kind of reliability.
In fact, I'm very strict about keeping on top of my dependencies because "spend a sprint to upgrade to the next version" is a process smell to me, no matter how seldomly it occurs. I have my Renovate bot set up to send PRs every Friday. Most of them are on "automerge on test success" at this point, then they soak in QA for the weekend and I send them to prod on Monday. The only trouble I can recall is with updating Kubernetes libs (let's not open that can of worms right now) and in some cases I have to manually intervene when libs upgrade from `interface{}` to using generics, but that's a refactoring that I gladly do.
Wired: Browser WASM (from Go source) that accepts commands over websockets to modify & render the DOM. (Is this "client hydration"?) Tight integration with htmlx (for pizazz + ruthless efficiency) and Javascript (for rich text editors + other advanced widgetry). This may require full Go, if tinygo continues to lack reflection support.
Why WASM? For what purpose? Just to do that? You don't need WASM for that: see Phoenix LiveView
And all the proto + protoc + protoc-gen-go + grpc + swagger/openapi + json-grpc-gateway dance is… not easy to understand at first