I've been "funemployed" for a few months and with all that free time and idle hands I wrote a full web framework (think Rails, not Flask) for Rust.
It's boring old MVC, has its own ORM, templates, background jobs, auth, websockets, migrations and more. If you're keen but don't feel like rewriting your app in a different language, Rwf has a WSGI server to run Django (or Flask) inside Rust [1], letting you migrate to Rust at your own pace without disrupting your website.
I think Rust makes a great prototyping and deploy straight to production language. Now it has yet another framework for y'all to play with.
Cheers!
(At some point this place has to contend with the issue of “we started as people trying to build cool things and wound up with every thread being nonstop complaints or nitpicking”.)
To be serious, good job!! Building a good framework is a shockingly large task, and it’s always nice to see people exploring the design space and trying for new ideas.
One of my biggest learnings from doing a bunch of web MVC through Rails over the years is that the framework should heavily discourage business logic in the model layer.
Some suggestions:
- Don't allow "callbacks" (what AR calls them) ie hooks like afterCreate in the data model. I know you don't have these yet in your ORM, but in case those are on the roadmap, my opinion is that they should not be.
- That only really works though if you not strongly encourage a service aka business logic layer. Most of my Rails app tend to have all of these as command aka service objects using a gem (library/package) like Interactor.*
* It's my view that MVC (and therefore Rails otb) is not ideal by itself to write a production-ready app, because of the missing service layer.
Also, curious why existing ORMs or query builders from the community weren't leveraged?
Disclaimer: I haven't written a line of Rust yet (more curious as the days go by). I'm more curious than ever now, thanks to you!
I like that... we need more (or better) opiniated frameworks a la rails/django in static languages.
As a heads-up, The Pages documentation page is blank.
[1] https://levkk.github.io/rwf/views/templates/templates-in-con...
From the ReadMe example, is there a way to use macros to simplify the following line of code:
async fn handle(&self, request: &Request) -> Result<Response, Error> {
I ask because many web developers don't come from a C/C++/Rust background - so the line above will be jarring/off-putting to many.(Awesome project btw)
Also, I don't really understand what is the reason for creating your own ORM instead of integrating with, let's say diesel.rs [0] and what is the reason for inventing your own template language instead of just picking one of the most popular existing template engines [1].
Other than that this project looks really interesting and I will definitely keep an eye on it.
Mind you, I don't assert that claim. I don't know; I'm not in web development. But I could see how having to learn a new framework that wouldn't pay back the effort would give rise to some valid complaints.
This is quite the claim. I despise service objects, personally. They end up scattering things around and hurt discoverability. There are other ways to do modelling that scale very well. There are a few blog posts on it, here's one from someone at Basecamp: https://dev.37signals.com/vanilla-rails-is-plenty/
This is of course very OO which I'm not a huge fan of. Elixir's Phoenix framework, for example, uses "contexts" which is meant to group all related functionality. In short they could be considered a "facade."
In any event, if you like services you like services, they can work, but saying MVC isn't enough for production-grade is a bit misguided.
I do agree that model callbacks for doing heavy lifting business processes is not great, though for little things like massaging data into the correct shape is pretty nice.
Rocket, Actix, Axum, Salvo, etc just to name a few. Each with different focuses (e.g. performance vs "batteries-included-ness")
[0] https://www.techempower.com/benchmarks/#hw=ph&test=composite...
The frameworks you listed are not a direct comparison to this lib, nor Rails, nor Django. They are Flask analogs. They are ideal for microservices, but are not a substitute for a batteries-included framework of the sort used in websites.
I love rust, but don't use it for web backends because there is nothing on Django's level.
As for templates, writing your own language is almost a right of passage into 30s+ nerd club. I never read the dragon book, but I always wanted to take that class in school. There will always be different implementations of the same thing, and writing this one that mimics very closely what ERB (Rails) does felt right.
Most of SSR I see is still SPA + Rest API/GraphQL backend with some scraper generating all the HTML.
Writing a stable WSGI server is possible, and not very hard with a bit of attention to detail, e.g. thread counts, vacuum (just like good old php-fpm, restart every n requests...), etc. Basically if you implement most options uwsgi has, you're on the right path. It's on the roadmap to make Rwf comparable to running Gunicorn.
less rails is... leptos, and a few others
I am curious where this comes from, because my thinking is the absolutely opposite. As much business logic as possible should belong in the model. Services should almost all be specific more complex pieces of code that are triggered from the model. Skinny controller, Fat Model, is the logic of code organization that I find makes code the easiest to debug, organize, and discover. Heavy service use end up with a lot of spaghetti code in my experience.
The other part is that from a pure OOP pov, the model is the base object of what defines the entity. Your "User" should know everything about itself, and should communicate with other entities via messages.
> Don't allow "callbacks" (what AR calls them) ie hooks like afterCreate in the data model. I know you don't have these yet in your ORM, but in case those are on the roadmap, my opinion is that they should not be.
This I agree with. Callbacks cause a lot of weird side effects that makes code really hard to debug.
my suggestions:
- async-trait should be stabilized now, so you shouldn't need the macro anymore
- Add opentelemetry integration so we get metrics and tracing out of the box
- use jemalloc for linux targets
Good work! Keep it up!
I tried to use standard async traits, but they don't support dynamic dispatch [1] which Rwf uses extensively.
I'll be adding opentelemetry tags to functions (from the `tracing` crate). jemalloc can be added to the binary apps that use Rwf, we don't need to add it as a dep to the lib.
Cheers!
[1] https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-trait...
It's also the oldest/most mature tool out there
The one I've seen he most is stuff like `UserRegistrationService` or the like. These things can end up getting scattered and in general, I would rather just talk to the business object, ie, `User.register` which can delegate to a more complex private object handling it. It's basically "inverting" things. The win here is that things are more discoverable (You can look at the user object and see a list of everything it does) and more importantly draws better boundaries. This way the web layer only has to know about `User` instead of `RegisterUserService` and `DeleteUserService` etc.
Again, services can work and aren't inherently bad, but plain MVC can certainly also work.
In a fully typesafe world, it should be pretty hard to derive from the shema this way.
I think most people agree about skinny controllers but I've definitely seen disagreement on if that gets moved to fat models or service objects.
Re templates: I understand that writing a new template engine can be a very fun task (it is both hard enough not to be boring and easy enough not to feel daunting). I also thought many times of creating my own template engine to fix things that I don't like in the language that I am currently using (mostly jinja2).
But if you intend this project to become an actual production ready solution, I see a lot of good reasons not to reinvent template language:
1. Forcing users to learn yet another template language is an additional entrance barrier
2. Tooling support. Existing languages might already have a good tooling support (coming from Python world: PyCharm supports both Django templates and jinja2 very well) including syntax highlighting, auto-complete, auto-formatting, linting etc. Are you going to create all of it yourself?
3. You mentioned planned migration from Python. How exactly I am supposed to manage templates during the transition period? Do I have to have two copies of each template: one in legacy language and one in your new language? If you had a template language compatible with Django/jinja2 [1] this problem would not arise.
4. Whether we like it or not more and more people are using LLMs for coding. This potentially could solve the issue of migrating templates. I expect LLMs to perform really well on the task of "translating" a template from a <popular template language A> to a <popular template language B>. The problem is that if your template language is brand new, LLMs probably didn't have enough examples in their training sets to "learn" its syntax. So, basically, you are setting up your users for a boring, tedious and error prone task of rewriting each template manually without a proper support from their IDE/editor. Meh.
BTW, Django makes it very easy to bring your own template engine [2].
[0] https://en.wikipedia.org/wiki/Not_invented_here
[1] https://github.com/mitsuhiko/minijinja
[2] https://docs.djangoproject.com/en/5.1/howto/custom-template-...
> I am curious where this comes from, because my thinking is the absolutely opposite. As much business logic as possible should belong in the model.
The opposite of this is what Fowler has called an "Anemic Domain Model"[0] which is ostensibly an anti-pattern. What I've learned from my own experience is that with an anemic domain model, the biggest challenge is that the logic for mutating that object is all over the codebase. So instead of `thing.DoDiscreteThang()`, there could be one or more `service1.DoDiscreteThang(thing)` and `serviceN.DoDiscreteThang(thing)` because the author of `service1` didn't know that `service2` also did the mutation.Domain models are hard to do well and I think the SOA era brought a lot of confusion between data transfer objects, serialized objects, anemic domain models, and domain models.
Other contenders were Loco (but was TOO much like Rails) and Rocket (whose macros only started to bother me after writing more Rust).
Your framework seems to perfectly match my criteria of "batteries-included, but not too prescriptive". Great addition to the ecosystem!
Also Django signals, Symfony events... makes things extensible but also hard to debug indeed.
So definitely a Flask, not a Django. And I want no Flask.
> why anyone would reach for Python for a webapp in 2024
Because it works damn fine, is complete and stable, has a gigantic ecosystem covering virtually every needs in the field and also we know the ins and outs of it.
Of course, less resource consumption is always good, particularly RAM, hence why we're interested in initiatives like RWF or why I keep an eye on the Go ecosystem.
1 & 2. It's not really a new language. It's very similar to ERB, so existing tooling, including syntax highlighting, etc., shouldn't be an issue.
4. LLMs are actually pretty good at "understanding" programming language syntax and replicating it to generate code, so even a new language would work. Besides, there is really nothing new under the sun, so similarities with existing languages would help here.
3. I migrated once from Jinja to Sailfish [1], it wasn't so bad. All template languages are roughly the same: start code tag, some simple code, end code tag, maybe a loop or an if statement, but the vast majority of it is print a variable. It would be nice to bring your templates over during a migration, but they are typically a small portion of your code compared to business logic, so I don't think it'll be a roadblock, if someone wanted to attempt this at all.
Mine has a table spec that can translate into a SQL definition or spit out a nicely formatted HTML form. There’s a separate controller that handles all DB connections / CRUD operations, with before- and after-hooks that can easily cross reference with other tables and constraints if needed.
It all works pretty nicely, although I would still switch to Laravel in an instant if I could.
I pushed hard and was able to get us to the point where stuff runs in PASE with modern languages (like PHP).
It’s not any specific licensing issue, just organizational distrust of anything that isn’t paid for.
I think makes sense when you application grows larger. Domains become more complex and eventually how data is persisted can become quite different from how it is represented in the business domain.