livebud / bud

The Full-Stack Web Framework for Go
MIT License
5.53k stars 181 forks source link

Refactor architecture #347

Closed matthewmueller closed 1 year ago

matthewmueller commented 1 year ago

This PR refactors Bud's architecture to what I hope will be the final major iteration for the foreseeable future.

Stepping back a minute, over the last several months, I've taken a significant detour from v0.4 Hosted Docs. So what happened?

Well, I've spent much of this time trying to figure out the right architecture to allow developers to bring their own generators. In the same way that Bud turns a directory of controllers into routes on your web server, you should be able to build your own "controller generator" that does something entirely different (e.g. GraphQL generator anyone? 🙃)

I was planning on deferring this whole custom generator business until after the framework was more feature-complete, but it's come up while trying to implement syntax transformers in the views. I want to be able to render Markdown on the documentation site but I didn't want to bake a Markdown renderer into Bud itself. You should be able to configure and even swap out the Markdown renderer with something else. Same goes with Tailwind support or React views. So that's why I'm building custom generators six versions earlier than planned 😂

I was pretty uncompromising in my goals for custom generators:

These goals led to some challenges in coming up with a solution:

  1. Triggering project-specific Go code from the Bud binary: Go is a static, compiled language with very limited options for dynamically loading code. In order to be able to run custom code, either written by or imported by the application developer, we need a way to compile this custom code separately and communicate with it from Bud. This requirement led to the rest of the complexity.

    Side note: Go's plugin mode actually fit the bill for this problem, but I lost trust in it after failing to compile a package that the default compiler had no problem with. It's also quite slow. It seems like it doesn't use the build cache nearly as aggressively as the default compiler.

  2. Sharing the generator cache: As Bud grows larger, the generator cache has become increasingly important. Without a cache, there's a significant amount of duplicate work happening as the generators request the results of other generators triggering regenerations. I wanted a way to share the cached results across processes and bonus points if the cache could persist on disk.

  3. It can't just be a part of the application binary: I was originally thinking maybe I could just squeeze this generated code into the user's application binary, but then you realize "oh, you need the generators to build the application binary in the first place".

After a couple of tries, I landed on the following design. I'm sharing this now because I'm fairly confident in the flexibility of this design going forward:

As a quick recap, Bud's workflows are the following:

Here how the various components fit together:

This may all seem very convoluted and confusing, but it gives us this dynamic loading capability we need while providing us with live reload. It's also surprisingly fast. Live reloads take under 50ms for frontend changes and under a second for backend changes. Initial startup time is somewhere between 250-600ms. This is all with synchronous syncs, no caching between syncs, uncached Go binaries, and roundtrips to evaluate JS code.

I still want to do a bit of cleanup after this PR and do some more testing. Once I'm confident, I'll cut a new release. You shouldn't notice any major differences, likely just different delays for backend changes. I removed the import hasher called imhash that helped us avoid rebuilding previously built binaries. This made prior changes nearly instant, but fresh changes were slower. This now makes fresh changes faster, but prior changes slower.

In the coming months, you should notice a re-focus towards new code generators (new features!) and getting the website up!

Okay, with all that said, I just want to say, thank you for your continued interest in Bud! I know it's been slow, but I hope these steps will set Bud up on the right path to eventually being your go to tool for painlessly building ambitious full-stack apps for your business. 2023 is going to be an exciting year for Bud. I really hope you'll come along for the ride.