After years of working with web frameworks in Python and Java, and then picking up Go along the way, I've come to appreciate Go's approach much more. That is, with a rich and capable standard library, you really don't need traditional frameworks. Need an HTTP server, router, etc.? Use stdlib. Need templates? Use stdlib. Need an ORM? You don't, but you may want to consider a small 3rd party query builder library of your choice. And so on.
This avoids depending on a complex framework that may or may not exist in a few years, improves security by minimizing the amount of 3rd party dependencies, keeps the learning curve low for any new developers joining the project, and is more flexible and easier to maintain. I don't have experience with Rust, and judging by the comments here, web frameworks might still be useful for it. Which is a shame, since the batteries included stdlib approach is far superior IME.
Anyway, I don't want to shoot down your efforts. Congrats on the launch and good luck!
Remember the PHP 4 era. The average developer is incapable of implementing everything from scratch securely. Django and similar big frameworks perform a vital public service, by providing sensible defaults to people who need them.
I do agree that personally I don't like using big frameworks. My personal favourite is the express architecture: a thin substrate that allows for middleware plugins to be installed and not much else beyond that.
I've also come to really appreciate a good standard library. I used to POC things in node because it's so simple, but the dependency tree is hard to justify.
I do like Rust's stdlib too, but its a different tool for a different project. My issue with Rust is the refactor cost when it needs to change.
Orms in go make little to no sense. As soon as one has a little bit more complex scenario, one ends up in the raw sql string mode anyway…
Like, try to build dynamic nested ands with ors using jsonb with gorm. Maybe there are two people on this planet who would like “aha, I know”. Just use your standard sql strings with prepared statements.
By definition, you only need to do the minimum to move data from one process to another to get things read or written.
That doesn't mean you need an entire system to represent every table as an object-like thing (which is what I assume when people say ORM). It's actually possible to just emit queries to read and write the exact data that's required in the shape that makes sense for the caller.
You can map objects to db updates, and map query results to objects. Neither of those objects needs to have a mapping to actual relations, like how ORMs insist on.
Mine doesn't, so I don't need an ORM. Plus, I think that when people say ORM, they mean more than just a map from the relational model to the object model and back. They refer to things like hibernate and SQLAlchemy and all the extra stuff that comes with that, like the active record antipattern, and a query builder which successfully encapsulates all trivial queries (which don't need encapsulation) but then as soon as you need to do anything even remotely complicated, the abstraction leaks. I'll be honest, not a fan of ORMs.
This is a major component of what drew me to love C# so much for a while. It's also one of those Perfect StdLib(tm) languages. Many of the components do need to be downloaded through nuget; but they are a part of the stdlib!
A rich stdlib establishes patterns and provides standard types for other libraries and frameworks, making the whole thing generally more consistent as opposed to something like the Node.js ecosystem, which looks and feels more like a bunch of random parts crudely fitted together through copious use of duck tape and glue.
Nice, congratulations. It must feel so surreal launching this!
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!
> 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.
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.
> 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.
I tend to draw the line at intrinsic vs extrinsic behavior. The model layer must be able to maintain all intrinsic properties. Whenever it would talk outside the application, it's beyond the domain of the model.
Taken to the extreme, you could model all intrinsic constraints and triggers at the relational database level, and have a perfectly functional anemic domain model.
A model should as closely as possible represent what it is (a table in a DBMS), not what it wants to be (the thing that the table is representing).
Otherwise you have two models, the model in your web framework and the model in your DBMS.
I would take this a step further and suggest that the term "model" is unhelpful and should be eliminated and replaced with the term "table" which is much more grounding.
Business logic should sit in the domain model, but not the orm model. The domain model should be an object that is not coupled with the web framework. In the Clean Architecture approach this is called an Entity.
One of the simplest examples is that you could have a Login domain model that handles login-related business logic, that mutates properties in the User ORM model.
All your login-related business logic code goes in the Login model, and any "the data _must_ look like this or be transformed like that" logic can go in the ORM model. If some other service wants to do anything related to the login process, it should be calling into the Login domain model, not accessing the User ORM model directly.
This sounds to me like the standard OOP versus Data Oriented programming divide. You want to think of code as a bunch of bundles of data and associated functionality, GP wants to think of code as data models and services or functions that act on them.
In general, I think 'unit test' level business logic should be in the model (think configuration for data-driven workflows, normalization logic, etc) but 'integration test' business logic should be in a service (callback logic, triggering emails, reaching across object boundaries, etc).
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.
Such a simple thing, but so many organizations love to set up their projects in ways that make attaching a debugger surprisingly tricky.
Even the most basic text editor and pretty much every language support interactive debugging - but if you set up a bunch of docker containers in a very careless way, you end up introducing a layer that disrupts that integration. It's fixable, but for that you need to think _a bit_ about it, and most devs I meet these days are like "eh, why do an interactive debugger, print statements exist" (and then be like "oh no signals are hard to debug :(").
It's because people ended up with models that were thousands of lines and difficult to reason about. Out of curiosity, did you end up running into this issue and how did you deal with it?
I work on a few projects that do have a model that is over a thousand lines long. A lot of times as the model gets more complex, you start moving associated model logic into their own models, which helps reduce the problem space. I think its fine because the logic ends up being cohesive and explicit. Whereas services end up with logic being hard to track down when they get very large and usually scattered.
Interesting. I’ve rolled my own PHP ORM at work (forbidden from using FOSS libraries like Laravel) and found hooks to be extremely useful. Notably, my programming experience started with PHP for Wordpress which used hooks extensively, so maybe I’m biased.
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.
Please don't feel obligated to answer if you can't, but why can't you use FOSS libraries like Laravel? Are you not even allowed to use MIT licensed stuff? What industry do you work in?
Small aerospace company. We had a really old school CEO at the time the project was started - didn’t even want us using GitHub since it was on the cloud. Everything runs on an on-premise IBM i Series (AS400 / IBM Mainframe).
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.
Thanks, that is quite fascinating! I recently spoke with a very old school IT guy who was setting up his brother's IT stuff for a new business, and he is militant about on-prem and other stuff too. It's a very interesting mentality, though so foreign to me as I strongly gravitate toward FOSS instead of away from it.
Different person. In the 2010s I was at a big co for which any "installation" of code had to go through procurement or some big architectural review, because the whole system was built around customer facing products, not internal tooling.
So when we needed a wiki (before confluence was bought and we only had file shares) I put dokuwiki on a server that already had apache and PHP from years prior. When we wanted to build internal web guis for automation and jobs, we used Bottle.py, since it can run a web server and operate without installation - just copy and paste the library.
> * 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.
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.
It would help a lot if you would clarify what you mean by “service object”. In my experience a single method on a service object would define a transaction. Is that what you mean by “service object”?
I think service is an overloaded term. It's so generic that you can probably attach dozens of meanings to it, but here's two: One interpretation is a piece of code that doesn't neatly fit in one domain object (domain service). The other is a piece of code grabbing stuff from the db, orchestrating some domain methods, maybe wrapping it in a transaction, and exposing all that as an endpoint (application service). I think one of you has one in mind and the other the other.
Along the lines of what OP is talking about, part of the problem is that Rails has no service objects, so I have seen a handful of different ideas of what they mean (probably no more than 10).
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.
I feel like the same people that like UserRegistrationService will argue that database table names should be plural because it reads better, which is wrong for similar reasons.
What even is a "model" if it doesn't have business logic? It sounds like you just want your model to be built from structs (that you call models) and procedures (that you call services). You can do that, but it can be quite hard to reason about what ways an entity can be updated, because any number of procedures could do it and all have their own ideas about what the business rules are. At this point your procedures might as well write back to the db themselves and just get rid of the "models".
Some people use the ORM models as pure persistence models. They just define how data is persisted. Business models are defined elsewhere.
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.
I would kinda expect REST framework to be able to generate Swagger (aka OpenAPI) definitions out of the box. That's one of the killer features of FastAPI in my opinion.
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.
I tried Diesel years ago, it was too "Rusty" for me. It made you define your schema twice, and couldn't use the same structs for inserts and selects (so 3 times, really). Overall, great first attempt at matching database types to Rust types, but the ORM needs to be more flexible and ergonomic - it's supposed to make writing queries easier, not harder :)
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.
Same: I was put off by keeping track of models in triplicate, and the lack of automatic migrations. These are considered features, vice bugs; it's not for me.
I tried diesel about 5 years ago. I needed to do relationships where the foreign key lived on the other side of the relationship from what diesel wanted. IIRC diesel only supported the key on a specific side, I think it was a M:1 relationship. Diesel docs said this was unsupported. I was still learning traits at the time but navigating how to implement this in diesel was beyond me. I used sqlx and moved on.
Re ORM: fair enough, I don't have any experience with Diesel, just wanted to know if you have an actual reason or if it's just a case of NIH [0] syndrome :)
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].
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.
Know of any similar frameworks that work the other way around? Where you can Keep an openapi definition as the source of truth and validate that your server follows it, I mean.
I agree. API-first is the way! Change your schema, auto-generate types into your code and use them for your server definition. It's just faster and more secure this way. Use api-fiddle.com or spotlight.io to work with the schemas (branching, sync with Github).
In a fully typesafe world, it should be pretty hard to derive from the shema this way.
Rswag is still my favorite openapi-related project. You write integration tests against the server and get an openapi spec as output. But thats for rails.
As others suggested, I would diff against a generated one, then potentially treat the generated one as source of truth in the future... Then diff accordingly as it changes.
What an amazing name choice, certainly one way to end up at the top of search results :P
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.
Rust projects tend to go for these broad, generic, self-aggrandizing names, and I honestly think it’s a huge mistake. Crates.io is just going to become a graveyard of abandoned projects squatting on legit-sounding names.
As SRE, I got interested in https://levkk.github.io/rwf/migrating-from-python/. On one hand, this is crazy neat you were able to pull it off. On the stability SRE hand, I'm internally screaming. At scale, this should be handled by Reverse Proxy (Caddy, Nginx, Traefik, whatever)
I thought the same thing, but this allows you to test your changes locally as an application engineer, without the back and forth. This goes back to the good old monolith vs. microservices debate.
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.
Honestly, including an ORM as a built-in 'feature' of a web framework seems like a quaintly reasonable idea at first, but I'm 90% certain that eventually it's going to become either (or both):
* it's own project because maintaining it will take up so much of your time.
* be decoupled from the web framework because users will want to use another because your own doesn't have abc feature.
From observing the ecosystems in .NET, Java and PHP, lots of people have lots of opinions on how ORMs should work.
Sounds nice, years of Django dev (with some other dev sprinkled in) has really taught me the value of boring old MVC and the rest of the ingredients, will def be having a look.
I've been evaluating and building out small prototypes with all the usual suspects for backend Rust work. So far I've reluctantly agreed with the hive mind that Axum on balance is the best choice, despite being lower-level than I'd like.
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!
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.
Nice got rustpilled myself recently through ditching webpack js loaders and using rust ones which are 50x faster, rust is so preformance enhancing, c++ and rust are my favourite languages atm.
Awesome, looking forward to testing it out. I really like that idea of being able to gradually migrate WSGI (Django) apps, or even support running both at the same time.
Cool! Since I learned Rust I've wanted a Django replacement that has the functionality of a batteries included Web Framework + the speed/footprint of Rust. I'll check it out!
Yup, they are called "macros". Rwf uses a few of them, some of which you'll find familiar, e.g. `render!` which returns a rendered template with HTTP 200 [1].
Rust can be an intimidating language but the example you’ve provided there really shouldn’t be intimidating to anyone that’s using TypeScript today. There’s a little learning to with &self and & but that’s really basic Rust stuff. I don’t think it’s wise for a framework to attempt to hide core components of the language.
You might be surprised, with Typescript's ubiquity in the web space the type definitions probably won't be too scary. I've never used Rust but I assume `&` is some kind of Rusty pointer.
You definitely can. I remember learning Django and Rails as a beginner, it wasn't straight forward. New things are hard until they are not new. Good luck!
Is it? Asking as someone not very tuned into the ecosystem. Based on TechEmpower's Web Framework Benchmarks[0] and AreWeWebYet's resounding "yes!" for years now[1] I always got the impression that there were quite a few options available.
Rocket, Actix, Axum, Salvo, etc just to name a few. Each with different focuses (e.g. performance vs "batteries-included-ness")
No, it's there but it's not popular and probably won't be for a while. Higher level languages like Java/JS/.Net/Go already do the job well enough for vast majority of use cases. Sure, there are cases like Discord where Go performance was impactful to their operations but those are pretty niche edge cases. Vast majority of people don't have those edge cases so any GC stutter is fine.
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.
Rocket comes with support for templating, cookies, websockets, middleware, an orm, testing, etc. I'm not familiar with Python web development (or why anyone would reach for Python for a webapp in 2024 :P), but it seems pretty analogous to Rails
"The goal is for functionality like templating, sessions, ORMs, and so on to be implemented entirely outside of Rocket"
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.
How many people are greenfield new Django style projects? I know Static Server-Side Rendering is becoming new hotness but I still thought pure Server-Side Rendering is frowned upon.
Most of SSR I see is still SPA + Rest API/GraphQL backend with some scraper generating all the HTML.
I would have argued Rust isn't the right choice for a web framework unless the team is rust-first because the memory guarantees aren't really needed, and you're better off with occasional GC pauses and faster development velocity.
This is perhaps a bit off topic, but I don't think rust is a one-trick-pony IRT memory safety. I hear this a lot, mainly from Rust programmers. [surprisingly]
That’s my take as well. To each their own, but for me there are other, GC-ed, languages that are performant enough and way more productive (and I love Rust!)
Lately I've been following https://loco.rs/ as it aims for a rails-like experience, complete with generators for workers, controllers, etc. I've only had time to experiment but it's the closest I've gotten to feeling rails-y in rust.
These kinds of comments seem to be like a fire starter on this site, but I cannot for the life of me see how they fit in the site guidelines.
(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”.)
I think there is a valid claim to be made that web frameworks cost more to learn than they pay off in value in using them.
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.
haha yeah it was a bit tongue in cheek as I'm learning another framework right now. If we can't have a little levity I'll just delete my comment if it upsets you. :)
This avoids depending on a complex framework that may or may not exist in a few years, improves security by minimizing the amount of 3rd party dependencies, keeps the learning curve low for any new developers joining the project, and is more flexible and easier to maintain. I don't have experience with Rust, and judging by the comments here, web frameworks might still be useful for it. Which is a shame, since the batteries included stdlib approach is far superior IME.
Anyway, I don't want to shoot down your efforts. Congrats on the launch and good luck!
I do agree that personally I don't like using big frameworks. My personal favourite is the express architecture: a thin substrate that allows for middleware plugins to be installed and not much else beyond that.
I've also come to really appreciate a good standard library. I used to POC things in node because it's so simple, but the dependency tree is hard to justify.
I do like Rust's stdlib too, but its a different tool for a different project. My issue with Rust is the refactor cost when it needs to change.
Like, try to build dynamic nested ands with ors using jsonb with gorm. Maybe there are two people on this planet who would like “aha, I know”. Just use your standard sql strings with prepared statements.
You can write your own or you can use someone else's. Those are the two choices.
That doesn't mean you need an entire system to represent every table as an object-like thing (which is what I assume when people say ORM). It's actually possible to just emit queries to read and write the exact data that's required in the shape that makes sense for the caller.
Mine doesn't, so I don't need an ORM. Plus, I think that when people say ORM, they mean more than just a map from the relational model to the object model and back. They refer to things like hibernate and SQLAlchemy and all the extra stuff that comes with that, like the active record antipattern, and a query builder which successfully encapsulates all trivial queries (which don't need encapsulation) but then as soon as you need to do anything even remotely complicated, the abstraction leaks. I'll be honest, not a fan of ORMs.
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 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.
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.
[0] https://martinfowler.com/bliki/AnemicDomainModel.html
Taken to the extreme, you could model all intrinsic constraints and triggers at the relational database level, and have a perfectly functional anemic domain model.
Otherwise you have two models, the model in your web framework and the model in your DBMS.
I would take this a step further and suggest that the term "model" is unhelpful and should be eliminated and replaced with the term "table" which is much more grounding.
One of the simplest examples is that you could have a Login domain model that handles login-related business logic, that mutates properties in the User ORM model.
All your login-related business logic code goes in the Login model, and any "the data _must_ look like this or be transformed like that" logic can go in the ORM model. If some other service wants to do anything related to the login process, it should be calling into the Login domain model, not accessing the User ORM model directly.
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.
Also Django signals, Symfony events... makes things extensible but also hard to debug indeed.
Even the most basic text editor and pretty much every language support interactive debugging - but if you set up a bunch of docker containers in a very careless way, you end up introducing a layer that disrupts that integration. It's fixable, but for that you need to think _a bit_ about it, and most devs I meet these days are like "eh, why do an interactive debugger, print statements exist" (and then be like "oh no signals are hard to debug :(").
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.
So when we needed a wiki (before confluence was bought and we only had file shares) I put dokuwiki on a server that already had apache and PHP from years prior. When we wanted to build internal web guis for automation and jobs, we used Bottle.py, since it can run a web server and operate without installation - just copy and paste the library.
Tldr bureaucracy leads to shadow IT.
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.
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.
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.
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.
[0] https://diesel.rs/
[1] https://crates.io/categories/template-engine
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.
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-...
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.
[1] https://github.com/rust-sailfish/sailfish
In a fully typesafe world, it should be pretty hard to derive from the shema this way.
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.
"Row" would be another good name choice, that would also be easier to say than 'rwf'.
RustOnWeb.com is even able to buy for $10 :)
Just say'n
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.
* it's own project because maintaining it will take up so much of your time.
* be decoupled from the web framework because users will want to use another because your own doesn't have abc feature.
From observing the ecosystems in .NET, Java and PHP, lots of people have lots of opinions on how ORMs should work.
I like that... we need more (or better) opiniated frameworks a la rails/django in static languages.
rocket.rs, actix, axum, warp, gotham, ruille
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!
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...
Why is that? Or, why isn't it required for other targets?
As a heads-up, The Pages documentation page is blank.
https://levkk.github.io/rwf/controllers/pages/
I ask because I imagine a simplified (Rust) syntax would be more inviting to newcomers.
[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:
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)
Yes, I was thinking of adding a shorthand for that. Will add something soon!
How?
Sorry what? Isn't Rusts whole thing is that it prevents you from prototyping wild ideas, in the name of memory safety?
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...
[1] https://www.arewewebyet.org/
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.
less rails is... leptos, and a few others
It's also the oldest/most mature tool out there
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.
Most of SSR I see is still SPA + Rest API/GraphQL backend with some scraper generating all the HTML.
(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”.)
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.