Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Rust Web Framework (github.com/levkk)
488 points by levkk 3 months ago | hide | past | favorite | 258 comments
Hi everyone,

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!

[1] https://levkk.github.io/rwf/migrating-from-python/




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.

[0] https://martinfowler.com/bliki/AnemicDomainModel.html


>the biggest challenge is that the logic for mutating that object is all over the codebase

Just use immutable data structures and be done with it. In departing from old OOP views and becoming more functional programming and data oriented programming friendly, C# introduced Records, which are immutable. Probably Java and Python have similar constructs. Javascript allowed the use of immutable data since long time ago.

If you insist of using fat models, you will still mutate the data all over the place by doing calls, but you just obfuscate it.


> Probably Java and Python have similar constructs

In Python, the closest you can get is a "frozen" dataclass, but you don't get true immutability[0]. What you _do_ get is effective enough for just about all practical use cases, except for the standard foot guns around mutable data structures.

    @dataclass(frozen=True)
    class MyModel:
        ...
[0]: https://docs.python.org/3/library/dataclasses.html#frozen-in...


You can redefine the byte representation `True` corresponds to in python. "Immutable enough" is all you're really looking for; it somebody goes out of their way to mutate the thing then they probably had a good reason for it.


Ahh, Fowler. The author that gave the World such gifts as Dependency Injection, Inversion of Control and other over-engineered "patterns". This is just my opinion obviously, based on experience spanning from the early 90s.


That's like blaming Fleming for the antibiotic crisis. Just because you have a pattern, you shouldn't use it preemptively.


Agreed, although the Java culture took the patterns and applied them in a cargo-cult frenzy. I do think the likes of Fowler and the so called Gang of Four are to blame for many of the Sun's later mistakes in API design and for the culture of patterns-everywhere in that era.


Imho, mutating the same object so many times, that a developer can't easily infer already applied changes is also a strong code smell. Fat models tend to encourage it, since all the mutation logic is available to all the services.


There are ways of getting around this. For instance, the "mutating" code can be organized in the service layer in a single location.

For instance, if you are updating a ShoppingCart model, all of that code which creates/updates/deletes a ShoppingCart could be kept in the ShoppingService - which will also create/update/delete the ShoppingCartItem models which are the line items for each item in the carts. So you don't have one Service class per table - but rather one service class per module of functionality.


The pattern is not OOP but that hardly makes it an anti-pattern.

Personally my take is business logic should be in the services and object specific validation in the like can be in the model. Unless your business logic is meant to deal entirely with single object types at a time you can hardly fit it in the pure OOP dogma. A behavior that deals with ModelA and ModelB seems just at home on serviceAB as it does on either model, from an OOP sense.


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.


In our model we have "repositories" (they dont talk outside the application, they basically contain queries related to a specific db table), and "services" (they call models, do queries that we not related to a specific db table and may talk to outside the application).


> 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.

In my experience with fat models is that it works for trivial cases. Once you get more complex business rules that span multiple models, the question becomes which model should it be implemented on. For example in a e-commerce app you might need to evaluate User, Product, DiscountCode and InventoryRow tables as part of a single logical business case to determine the final price. At that point it doesn’t make much sense to implement it on a model since it’s not inherent to any of them, but a PriceCalculator service makes sense.


Exactly how we do services.

We have one model file per db table (a "repository") in which we define all queries that "logically belong to that table" (sure they do joins and/or sub-queries that involve other tables, but they still "logically belong to a specific table").

Once we need logic that combines queries from several "repositories", we put that in a "service" file that's named after what it does (e.g. PriceCalculator).

Most of our business logic is in the models (repositories and services), other encapsulated in the SQL queries. Repositories never call services. Model code never calls controller code. Pretty happy with it.


When you join two tables, which model does the query belong to?


We'd not call it a model, we have no notion of "a model", merely a package called "models" (in line with MVC separation).

We do have repositories. And when joining it could belong to both tables, and thus to both repositories. In those cases the devs picks one: the one that it most prominent to him.


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.


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.


This is the critical difference.

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.


> "the data _must_ look like this or be transformed like that" logic can go in the ORM model

I would rather implement the Repository pattern and leave the poor model as a plain data structure.


What's a difference between this domain model and the service then? In your example you'd have a Login service and all the code related to login would have to go through the Login service, right? Why do you need the additional domain model layer?


I think the ORM (with Entities) is an anti-pattern. It makes simple queries slightly simpler, and hard queries impossible to express: hence you will need a way to express hard queries.

Also Entities are usually mutable.

What clean architecture prescribes here VERY bad for performance. Some of your business logic will dictate how you write your queries if you care for performance.


>Business logic should sit in the domain model, but not the orm model.

Business logic should sit in a business layer.


We recently migrated from fat controller to fat model. We found that fat model makes the code a lot clearer and is much easier to test behaviour.


I'd argue that you shouldn't use a fat model, either. To me the best way is using as least code as possible in controller, no code at all in model and having service layers that take care of business logic, and layer for talking to the database.


Talking to the db should contain a lot of business logic if you want performant queries. I'd say the "service layer" and the "layer for talking to the database" (repositories) are all part of the model and all contain business logic.


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.


The "M" is just a package, a grouping in the structure of your code.

I agree there is no "a model", it should be "a record" or "a DTO" or "a repository" (which contains the queries to a particular table), or "a service" (that contains logic that calls several repositories).

The idea of having "a model" it closely coupled with the us of ORMs (which are an anti-pattern IMHO). They provide "models" or "entities" that try to be too much (wrap over a db record, contain logic, can back a form submission -- breaking the single responsibility principle on all counts).

I feel like "clean architecture" is trying to fix this, but only makes it worse.


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.


If I had to choose between thousands lines in models and thousands lines in controllers I'd definitely take "fat" models over "fat" controllers.


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.


> This I agree with. Callbacks cause a lot of weird side effects that makes code really hard to debug.

Also Django signals, Symfony events... makes things extensible but also hard to debug indeed.


attach a debugger to the running process


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 :(").


"debug" was a poor choice of word on my part. It's not about debugging, more about following the logic when the program is read by a developer.


That's fair enough, though again, interactive debugging can really help with understanding what's going on by just stepping through the call as it happens - just click "debug" on the test and play around with it.

But I'd agree the issue is real, and we're discussing mitigation of it, and whether it's sufficient. It's definitely possible to turn your code into aspect-oriented programming hell with careless use of signals, hooks and the likes.


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 do agree that models should not contain bussiness logic. Not having bussiness logic in models is what Martin Fowler and Robert C. Martin call "anemic domain models" and is contrary to how legacy OOP-heavy and pattern-heavy enterprise development used to be.

However, after +20 years of development, I've came to the conclusion that encapsulation is a burden, not a feature and data should be separated from actions that are being performed on that data. It's called data oriented design or data oriented programming, and I am far from the only one that came to the same conclusion.


I so much agree with this, I want to hug you.

All this encapsulation for sake of encapsulation, and interfaces everywhere even for implementations that the application is married to. I once heard a wise man say: most of these design pattern only add code! And we should be weary of adding code that does not add features. Sure in some cases it makes the code easier to understand/read/refactor. But in many cases it becomes a holy goal with very little actual benefits. Clean architecture being the epitome of this.

> data should be separated from actions that are being performed on that data

This view is shared by nearly all dev that prefer functional programming. I also consider this true. FP-langs help you to "keep 'm separated", and OOP-langs historically make this very hard.

If you build an app on top of a db, you biz logic will get intertwined in the queries and the some of the code that's close to those queries (i.e. model code). That code represents you business logic. Trying to write the biz logic separate from the db is --to me-- just a way to make your project go over budget and hurt your performance.


>OOP-langs historically make this very hard

I don't think is just the langs, but mostly the old culture around those. I do .NET development using a data oriented approach and nobody calls me an incompetent developer. I also teach junior colleagues to be vary of OOP-ness for the sake of OOP-ness and overusing patterns.

I am not aware of any OOP language that demands inheritance, encapsulation or method overriding. It's the people who do.

>If you build an app on top of a db, you biz logic will get intertwined in the queries and the some of the code that's close to those queries (i.e. model code). That code represents you business logic. Trying to write the biz logic separate from the db is --to me-- just a way to make your project go over budget and hurt your performance.

I tend to separate, when possible, business logic from DB logic and from other kind of input like calling into external APIs. But I do that trough layers. I put a repository over the DB and its job is to just fetch data from DB and deliver it as data structures. I use that repository in a business layer.


> I am not aware of any OOP language that demands inheritance, encapsulation or method overriding. It's the people who do.

But in Java all functions must be part of a class. All I want to say it that in OOP-langs it is possible but takes a lot more discipline. Where in FP-langs there's lots of guard rails and help in making sure you separate logic from data.

> I tend to separate, when possible, business logic from DB logic and from other kind of input like calling into external APIs. But I do that trough layers. I put a repository over the DB and its job is to just fetch data from DB and deliver it as data structures. I use that repository in a business layer.

I find a lot of biz logic ends up in our db queries in order to make things fast. Running the biz logic in the db yield big perf improvements. I cannot see how to do that in a layered approach.


The biggest reasons for me to use the clean architecture are faster testing (most logic is in functional code) and most changes having impact in a small number of files only.


> * 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.


At my last project, they did "service oriented development" and everything was either a service, a viewmodel or a test. For example aLogService, a ValidationService, or an AggregationService.


> I think one of you has one in mind and the other the other.

This is very possible.


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.


I don’t really follow. My focus wasn’t on the naming but the location of responsibilities.


I understood. Perhaps my mental connection was somewhat flimsy though. I was trying to make a tongue-in-cheek joke about how whenever I work on a database that has tables with overloaded responsibilities (poor normalization) instead of proper foreign keys they often also coincidentally have poor, plural names. Whenever I encounter such a thing, I think about the story of how it became that way. Usually part of the story includes a developer looking for a place to stick some logic and deciding an arbitrary place seems good enough because it’s a bit vague.


How are plural names linked to those things, though? I have a low-care level for naming conventions so long as there are conventions. Many successful frameworks use plural tables names. Though I agree singular probably makes a little more sense, especially since it eliminates the need for inflection code.


Your UserRegistrationService(s) is/are also prone to be overloaded. You don’t think of that as part of a naming convention? Would you argue that’s more about architecture? I was simultaneously agreeing with you and adding that this other minor organizational annoyance I have adds to the pile. Apologies if that seems low value.


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.


Are there not any Laravel shops that would take your money so you can "pay" for it?


Hah, I’m currently trying that tactic to get us on Bookstack for our SOPs. Never thought about doing it with Laravel. Could work!


Thanks for sharing that. AS400 always catches my eye after doing in internship at IBM, working with AS400 back in 2000.


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.

Tldr bureaucracy leads to shadow IT.


Thanks!

Re: callbacks. They are very nice, when you have CRUD endpoints that modify models directly from JavaScript [1]. It ends up being pretty DRY, especially since you'll find yourself modifying the same model from different places in the code, and without callbacks, you'll have bad data.

Re: service layer. It's a matter of taste, and you can probably avoid it until you're way in the thousands of LOCs. Rails and Django are all about building quickly - that's an advantage you shouldn't give away to your competitors. Service layer is a drag that you may need as an "enterprise".

Re: MVC not production-ready, we know that's not true, but appreciate the hot take, always a good starting point for a great discussion.

Re: existing ORMs, they were not flexible enough. I used ActiveRecord and Django as my inspiration; those are both excellent ORMs, while existing Rust ORMs lean too heavily on type safety in Rust in my opinion. The database should be the source of truth for data types, and the framework should allow for intentional drift.

Hope you get to try Rust soon. I've been using it for years, and I don't want to go back to Python or Ruby, hence this project.

Cheers!

[1] https://levkk.github.io/rwf/controllers/REST/model-controlle...


>* 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.

How is that so? Can't you add a service layer and call a service from a controller? I don't know about Ruby but for .NET and most Java frameworks this is possible.

To take an example from .NET frameworks, which I am mostly familiar with, you use WebAPI for web applications and MVC for websites. An API just returns data in JSON or whatever form and MVC returns HTML + javascript + whatever media and files.

A controller receives the HTTP request, does some logic, make DB requests, receives models and uses that logic to update a view and serves that view as an HTML file.

Controller - does actions and uses logic

View - describes how the page looks

Model - contains data

Nothing stops the controller to call a service layer which will call a data layer instead of just calling directly the DB.


Not OP, but I think that what s/he's saying. By itself, MVC is not complete for production apps, ie don't put business logic in the model &| controllers. Rather it must be in a service layer which is entirely dependent on the developer to provide.


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.

[0] https://diesel.rs/

[1] https://crates.io/categories/template-engine


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.


By skimming docs I notice that you use <%- %> tags for outputting un-escaped strings. In contrast ERB uses <%- and -%> for trimming leading and trailing whitespace of the surroundings. In security sensitive context of escaping output you might not want to appear as mimicing ERB while deviating at the same time. A user might end up using un-escaped output all over the place by accident.

But great work overall!


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.


Never been a fan of ORMs in general. Working with the extra abstraction layer on top of SQL can be painful.


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].

[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-...


In random order:

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


https://github.com/poem-web/poem is one Rust framework with swagger definitions out of the box.


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.


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.


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.


Just have to be a bit careful mentioning it in front of non tech folks who'll inevitably ask "what's arsewag?"


You can simply diff it. Define the OpenAPI spec, have the framework generate a spec, compare.


You could generate a spec for the service and then diff to the expected perhaps.


100% agree that generating OpenAI from your server is a killer feature, it works well for us with Dropshot.


>I would kinda expect REST framework to be able to generate Swagger (aka OpenAPI) definitions out of the box.

Me too, but, as I understand, this is a MVC framework that is returning HTML, not a framework to build web APIs.


It advertises "Built-in REST framework with JSON serialization" in the readme, that's why I asked.

Looks like it also supports some kind of models for JSON validation [0], it shouldn't be too hard to generate Swagger from them, just like FastAPI generates Swagger from Pydantic models.

[0] https://github.com/levkk/rwf/tree/main/examples/rest


Just FYI - since this is an asynchronous framework you probably would want something like sqlx versus Diesel (which is sync if I recall correctly)


diesel_async works great, and it's made by the lead diesel maintainer


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.


Sure, but Gunicorn doesn't try and run Node. I totally get benefit for development; I was just worried about someone YOLOing this into production.


I imagine the author assumed a technical audience wouldn't need to be told of the necessity of a reverse proxy in front of the wsgi server


As SRE, you assume 100% wrong. Devs totally need to be told "Please don't do this in production." They will ignore you but hey, you might reach a few.


As a Python dev, I imagined the same.


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.


such as?



> What an amazing name choice

"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


Not anymore, squatted : - )


> (think Rails, not Flask)

I like that... we need more (or better) opiniated frameworks a la rails/django in static languages.


Well done! You could try to get mentioned on https://www.arewewebyet.org/


Very surprising this page doesn't mention loco.rs which seems like the most "Rails" Rust framework out there.


Based! Django/Rails in a god tier language!

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!


Thank you!

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...


> use jemalloc for linux targets

Why is that? Or, why isn't it required for other targets?


jemalloc (as opposed to GNU libc and LLVM) sometimes performs better. [1]

[1] https://jemalloc.net/


I've tried to learn Rust but gave up early. Coming from C#, C/C++, Java, Python, Rust seems weird to me.

And it's not the weirdness that discouraged me, is the slowness of accomplishing one task comparing with languages like C#, Java or Python.

And since I mostly do web development these days, ease of use and productivity is paramount for me. True, with Rust there might be a bit more throughput and speed, but judging from latest web frameworks benchmarks both C# and Java tend to do very well in this regard.

Please, don't read this as a critique of Rust, or me trying to take away such a great accomplishment such as writing a MVC framework from scratch, but more of a "proper tool for the proper job" type of post.


Yep, totally get where you're coming from. Many people, myself included, have had a similar experience starting out. To push through the learning curve, you need a strong motivation--whether it's because you have to use the language, you're drawn to the benefits of the type system and safety features, or something else specific to you. If you don't have that kind of motivation, it's totally fine. I didn't at first either, which is why my early attempts to learn the language didn't go far. Like many others, I found that it does take longer to get things done in Rust compared to languages like Java or Python. There's just more upfront thinking required. That's valuable in certain situations, but if you're not clear on why you'd make that tradeoff, there's no strong reason to keep going.


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.


Oooh, mind sharing what you ditched web pack in favour of?


Still using webpack, upgraded from 4 to 5 but switched out babel and ts loader with swc.


Rspack maybe? It's compatible with webpack AFAIK


Thanks to both responders, I'll check them out.


not OP but you can try swc


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!


What about Actix? There were some discussion on HN a while back related to Actix vs Axum if I recall that.


I would love it if you could link that


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.


Congrats! I have a question: I used to have a job as a Rails developer, and one thing i struggled with was knowing which options/hash keys were possible to pass into various functions, due to the lack of typing. Would it be the case that your framework, based in a type-safe language, would give precise autocompletion of all such options? Or does advanced usage of Rust macros remove some of this ability? Thanks!


Thanks!

Rust macros are typed, so the language server should be able to give good suggestions. They are also optional, and you can always use the code they generate directly. In most cases, it won't be too verbose either.


Great work!! I was just talking about how this is a major gap in Rust and here you are the very next day! Looking forward to use and contribute!


What are these projects missing that you feel there’s a “major gap” in the web framework space?

rocket.rs, actix, axum, warp, gotham, ruille


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.


Looks cool! How does it compare to loco.rs?


I just noticed that REST framework returns 501 - Not Implemented [0] if the method is not implemented, which I think is really weird. I think that most frameworks return 405 Method Not Allowed in such cases.

Also, it is typically assumed that 5xx return codes are retryable while 4xx are not. So, I don't think that not implemented method should return 5xx, because it is pointless to retry it.

[0] https://github.com/levkk/rwf/tree/main/examples/rest#restcon...


> I don't think that not implemented method should return 5xx, because it is pointless to retry it.

Pedantically, that's not true - it can be unimplemented at T0 and then a deploy happens and it will be implemented at T1. You are likely thinking of retries within the milliseconds timeframe, but even that could be true in a race-condition-esque situation where a redeploy was happening and the request just got unlucky

In the context of this thread, not allowed is quite different from "happens to not be present." I think of the distinction like 404 versus 410 Gone <https://http.cat/status/410> in a temporal state


Interesting. I should reread the REST spec. I put not implemented as more of a tip to the developer to implement this method at their earliest convenience.


Another related submission from today:

Ryde: A Rust Web Framework - https://github.com/swlkr/ryde (October 2024, 5 comments)


```I think Rust makes a great prototyping and deploy straight to production language.```

How?


Thanks for this. I've been looking for a Django-esque web-framework for Rust for a while now, and it would seem I'm not the only one[1].

Also, if you're open to suggestions, use SemVar, and have releases on GH so we get email notifications.

[1] https://news.ycombinator.com/item?id=41760421


You got it. I will setup a GH action to release to crates.io at the same time.

Cheers!


@levkk How do you thing it compares to loco.rs, I'd like to understand the differences.


Do you plan on adding CRUD generators? That was the killer feature of MVC frameworks


It's there but the docs aren't written yet. See the REST example in the repo. I'll have some docs for that soon.


Thanks for sharing!

As a heads-up, The Pages documentation page is blank.

https://levkk.github.io/rwf/controllers/pages/


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!


> boring old MVC

> Written using the classic MVC pattern (model-view-controller)

well, the "classic web" version I assume. I've never seen a web context in which the original MVC model was used.


Looks great, very interesting! How is the state of to documentation?


Not bad, but can be improved. I've been putting work into it lately, will keep adding more. Lots of examples in the repo as well.


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.


I can already hear people asking "Did you aRWF already?" Seriously, the migration option is precisely how I think migration for years. Great job!


Does Rust have any DSL for web use (e.g. Rails in someways is a DSL to Ruby)?

I ask because I imagine a simplified (Rust) syntax would be more inviting to newcomers.


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].

[1] https://levkk.github.io/rwf/views/templates/templates-in-con...


That's good to know.

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)


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.


Coming from Typescript that doesn't look very ominous to me, though it would nice if the types could be inferred somehow.


Thanks!

Yes, I was thinking of adding a shorthand for that. Will add something soon!


for controller routing. I think pavex have great idea (https://www.lpalmieri.com/posts/pavex-progress-report-02/) for using rustdoc json


I love rust!! This is so cool and I'm a beginner and I'm not sure if I can utilize this framework or not.


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!


Surely it has a WSGI client not a server.


Very interesting. I might have to check this out after work!


Love it; this is a big gap in Rust's ecosystem IMO.


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")

[0] https://www.techempower.com/benchmarks/#hw=ph&test=composite...

[1] https://www.arewewebyet.org/


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.


Great question!

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.


So... rust "on rails" is basically ... https://github.com/loco-rs/loco

less rails is... leptos, and a few others


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

It's also the oldest/most mature tool out there


"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.


>or why anyone would reach for Python for a webapp in 2024

I'm out of touch.. why not?


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.


This is orthogonal; You don't use auth, email, automatic admin, migrations etc from a SPA; those are backend jobs.


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]


But what else does it really bring to the table compared to something like C# or Kotlin?


Excellent tooling (package manager, linter, compiler), exhaustive pattern matching, best-in-class mutation management, overall syntax and language features.


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!)


Impressive launch, good luck and happy coding!


> I think Rust makes a great prototyping and deploy straight to production language

Sorry what? Isn't Rusts whole thing is that it prevents you from prototyping wild ideas, in the name of memory safety?


“Wild application ideas” and memory safety are orthogonal.


What happened with PostgresML?


This looks very cool!


Hopefully it takes off.


Well done!


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 develop in Go every day and it's by far the worst language I've ever used.

Having to explicitly handle every type of error immediately without any support for deferring, encapsulating, chaining, manipulating, transforming etc is antiquated and tiresome.

Rust has Option and Result types and all of the niceties along with it.


All your complains are doable, I mean you work with Go daily and you don't know how to wrap errors?

https://pkg.go.dev/errors

Rust has all those nice things and yet you still need to import two of the most popular error lib to work decently.


That is not remotely comparable to proper Option/Result types.

And in Rust you don't need to import anything.


hear me out. golang quality stdlib, with Option & Result types from haskell or rust.


The dream. Someone posted this, which is designed along these lines, here not long ago. https://github.com/borgo-lang/borgo


Don't forget enums. And lets get rid of the insane := syntax that makes no sense when basically every function is returning 2 values. Not to mention shadowing bugs like this:

  var cursor int64 = 0
  for {
    rows, cursor, err := db.PaginatedRows(cursor)
    ...
  }


Native Option/Result types + algebraic data types would solve quite a few complaints I have about Go


It would solve many of my complaints as well.

But, to be honest to myself, I don't think anything will be too different in my day-to-day programming, nor that I would produce better code with less bugs.


Adts eliminate certain kinds of bugs like nil pointer dereferencing, and, if you model your types correctly, makes invalid states unrepresentable


Sounds like Scala Native: https://scala-native.org/en/stable


V is Go with Option/Result, enums, ORM etc:

https://vlang.io/compare#go


V is half-baked in many ways and shouldn't be considered for anything serious.

Odin[1] is a better alternative in the niche modern language space, and also shares many design choices with Go. It has operators like `or_return` and union types like `Maybe(T)`, which can in practice be used for improved error handling.

Though I honestly don't find Go lacking in this sense. The syntax is a bit verbose, but it forces you to think about how to handle every error, which is a good thing. There are far more important design topics worth discussing than this. Go usually makes more correct trade-offs where they matter compared to other languages, and most of the criticism it receives is unwarranted IMO.

[1]: https://odin-lang.org/docs/overview/


Odin can be considered even more "half-baked". It is not a mainstream "corporate approved" language either. It's one thing to suggest sticking with Go or using C# as an alternative, it is another thing to suggest languages like Odin, Zig, or some other almost unknown like C3. At least V has a place at the table, with being an actual useful alternative to Go. Has a significant following and hardcore fans, because V gives them the missing features that many Go users have been begging or looking for (enums, sum types, immutability, easier C interop...). Same can kind of be said for Zig, with having a significant following, and pushing it as an alternative compiler for C.

Anyone taking a serious look at Odin or C3 (strangely morphed from C2 into being more Jai-like or Odin-like), will know they are both no where near to being production ready or 1.0 (many years far away), despite both being old. Odin has less of a following, less contributors, minimal documentation, no books on Amazon, and many of its good ideals were admittedly "borrowed" straight from Jai. Many consider Odin a Jai-clone, where it would be better to wait on the "real McCoy", which would be Jon Blow's releasing of Jai to the general public. Jai fans[1] are about as hardcore as it gets. When Jai is finally released, nearly everybody will forget about Odin or C3 (even tsoding suggested dropping it for a public release of Jai), and very few people even know about them as it is. Just about whatever Odin (or C3) wanted or aims to do, Jai does.

[1]: https://jamesoswald.dev/posts/jai-1/ (Simplicity, Jai, and Joy)


It's not half baked lol.


Ah, you're the author. :D

I mean, kudos for sticking to it over the years in spite of the mountain of criticism, but I personally confirmed a few months ago that it's indeed half-baked. :) Certain features work sometimes, but often fail in cryptic ways. The documentation is a mixture of missing, outdated, wrong or aspirational. I've forgotten the specifics, but I remember being frustrated by it relatively quickly, and abandoning it altogether in less than a week. Good luck with it anyway!


That has been around for a long time now. V lang[1], child of Go and Rust, has them. That, and things like enums, sum types, etc...

[1]: vlang.io


Completely agree…

Forgetting to check any given err value leads to potentially undefined behaviour.


> I've come to appreciate Go's approach much more

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.


Interesting that Python has actively said no to recent web-based modules in the stdlib. Because that stuff moves too fast, and putting in the stdlib would hold it back. The most famous is requests, but I've heard the argument elsewhere.

Not sure who to believe. :-D


I've not done a lot of Go, but a former employer used it for all the back-end stuff, and they weren't using the standard modules for web stuff either (and it was at a time who didn't have a proper dependency management tooling so it was very tedious compares to modern languages).


> a former employer used it for all the back-end stuff, and they weren't using the standard modules for web stuff either

This might have been a long time ago but I can't think of much that you cant do. I've seen this behavior though on some project, but it was usually not about the stdlib's deficiencies but more so the developers lack of understanding what they're supposed to do and so revert to another lib.


It's never about stdlib “deficiency”, it's just that there are alternative ways of doing things in third party libraries and sometimes these alternatives are better than the original implementation which cannot evolve since it's in the standard library.


> My issue with Rust is the refactor cost when it needs to change.

That’s an interesting take. I’ve heard it before but I believe the opposite. For me, Rust is much easier to refactor.


Depends on your level of refactor, for small things, sure, like everything its simple. But try to change the workflow or modify the architecture in a certain way, it can be a real pain. I find it's a blessing in a certain way though as it makes you plan and consider a lot more in advance when starting a project/implementation.


Go for web development is like PHP for machine learning.

Go is a perfect language when dealing with files or network. Once you add database layer, calling external APIs, then you face the same latency of other languages due to 3rd party integration.


Agreed on all fronts. Go has nailed the stdlib and every other language is in its shadow


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!


Would it be a fair comparison? C# didn't have good support for JSON in the standard libraries for almost 20 years[1]. ADO.NET seemed like a failure to me as well, but maybe I just didn't understand it.

The System.Drawing namespace at least as of .NET Framework 4.5 required virtually all of its operations to be done through a System.Drawing.Bitmap, which meant that no matter how your image data was represented, it needed to get rasterized. This wasn't a big deal for most desktop/local use cases, but it's very inefficient, so if you had any servers relying on this, and had a reasonably decent amount of traffic to those servers, you could be getting CPU usages way too high.

As an example, I remember one time we were hosting 30 kb TIFF images (high res, 1 bit per pixel) for download, and generating the thumbnails was done in real time, which caused literally gigabytes in memory churn per request due to the rasterizations... IIRC I fixed it by using emscripten (this was 2015-2016) to do resizing and rendering in the frontend with a small C module using LibTIFF, then the server only needed to send the 30 kb files as is. Unfortunately I've had a hard time finding people to work with these days that give me that kind of room to execute.

Does Go's standard library have these rough edges or is it actually pain-free? I'm on the fence about whether I should learn Go or Rust while I am on a short employment break.

1. https://github.com/dotnet/runtime/issues/27761


I think every kitchen sink standard library has some rough edges. Java is also a kitchen sink language and it, too, has rough edges around certain areas. I don't know Go well enough to have an opinion on where its weaknesses are.

As for this question, > I'm on the fence about whether I should learn Go or Rust while I am on a short employment break.

What are you learning the new language for? If for fun, then this question is longer. If for employment, I would bet that there are more devops, devops adjacent and "systems developer" roles in the Go language.


Declaring a stdlib free of rough edges would be like declaring a complex piece of software bug-free.


Not needing an ORM made me laugh


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.


Why is that?


Your programming language has objects. Your database has relational tables. By definition, you need to map between the two.

You can write your own or you can use someone else's. Those are the two choices.


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.


> Your database has relational tables.

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.


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.


Query results are actual, if transitory, relations.

And db updates are either relations or tuples (and a tuple is a relation with cardinality of 1, so...)


You can be pedantic if you like, but this is obviously not what anyone means by "ORM".


Its literally the exact things that ORMs map between: query results -> objects and object changes -> database updates.


No, ORMs abstract away the relational database and present it as if it were some kind of object database. Needing to map query results to structs is just incidental, and is completely missing the point of an ORM.

If copying query results to a list of structs is enough to qualify as an ORM, then the term is so generic as to be entirely useless.


Only for basic objects.

The minute you add relationships your approach becomes unusable.

There is a reason ORMs have persisted for over 30 years.


No it doesn't. Why would relationships matter? You handle the relationships in SQL queries, and just copy query results to structs.


> copy query results to structs

Congratulations, you've invented an ORM.


This is clearly not what anyone means when they say they don't want an ORM.

An ORM library maps an entire relational database to a graph of objects, with the intention of abstracting away the relational database. Copying query results to structs doesn't actually do any of that.

https://en.m.wikipedia.org/wiki/Object%E2%80%93relational_ma...


Majority of ORMs really are nothing more than this.

But then it's nice to have something that generates optimised, database-specific SQL, can handle date/number conversion, supports many-many relationships, converting BLOB to binary streams etc.


That’s 1% of an ORM.


It's at least 30% of most ORMs. And if your homebrew ORM actually gets wide use, it'll grow from there.

Source: Wrote an ORM that other people actually use. Still adding features, twelve years later.


ORM by modern standard is not that, it's basically let me interact with the DB without knowing SQL.

Every language need to deserialize results into some object so by definition every language that deal with a DB do some form of ORM. So ORM is meaningless in that context.


There's a difference between mapping database tables to objects and just copying query results into structs.


I kind of agree with this. Data in a database is often relational (hey it might not be, but it's still nice to represent it in a struct sometimes, rather than 10–20 different variables which can change anytime your database changes).

I've been using Go recently, and while I'm not convinced on an active-record style ORM in Go (I don't think the language is dynamic enough, and I'm not the biggest fan of codegen), I've been loading the row data from Postgres into a Result struct (pretty much a 1:1 mapping of the Postgres result set into the struct), and then using another function to load the Result struct into struct with their relationships attached (using tags on the structs to define the relationships between them). This has worked great using reflections & generics.


And chance this is open source?

I’m building something similar and I can’t decide on the relationship struct tag syntax to use. Would be neat to see how others are thinking about it.


Why did it make me laugh? I thought the delivery was funny is all.


And Go compiles very fast, very few understand how important this is

Not only it is important for fast iteration, but it also contributes to the fun, waiting for compiler to finish is not fun

To me there shouldn't be any new language made without hotreload and incremental compilation

It's 2024, with the HW we have, we shouldn't have to wait more than 1 second to compile code

Fun fact, my game written in D (30k LOC), fully recompiles in 0.4 seconds (Intel i3 10400f), if i can do it, you can too


Every time this subject comes up for any language I praise Go for making it very straightforward to start a web server.


Do you also praise Python for having http.server?


Actually, I do for a different reason. I can go into any directory and type python -m http.server and now I have an improtu web server for a folder, so I can download files over my LAN.


That's a nice feature. You can also import it and it's a lot like the Go http server.


Except it's not meant to be used in production, where you must use shenanigans like WSGI and 3rd-party servers.


There are lots of Rust web-related libraries that are modular.


honestly sucks trying to build a large web app with just the go stdlib. The stdlib is amazing but it is not all you need for a good dx experience.


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.


Concretely, Go has eg context.Context, net.Conn, http.Handler and of course io.Writer/Reader/Closer. All 3p libs will use the standard types. This means you can compose many 3p building blocks together, without any prior knowledge or coordination on their end.

When you have an insufficient stdlib, you often get compatibility issues, where things don’t compose. So you get these kind of pseudo-std mega frameworks like Tokio instead. It was a while ago I was deep in JS but I remember similar things there.


Really? What do you feel that it's missing? I write Go for a living but I have also used Laravel (and vanilla php back in the day),and both actix + axum and I think Go really does hit that sweet spot of abstraction level for backends/web services.

I will say that I do prefer Gorm only to be used to scan the table -> model, I'd still rather write the sql by hand but I also don't want to scan the rows every time. But other than that I really cannot think of what it's missing.


Have you given sqlx a try? Manual query writing like database/SQL but convenience wrapper functions that handle the row scanning part for you. I haven't felt the need to use anything else for years now.


which Java version is the one you used last?


I've come to the conclusion that Professionally Pretty Products should use the JS ecosystem and Internal Web Dashboards should use Go. When your company is employing dedicated professionals for Product and UX, trying to sync their work to server-rendered Go HTML templates is just much, much harder compared to working with them in Storybook and the like. Yeah the dependency tree explosion is basically someone's full-time job to stay on top of, but when the company is already paying for Product and UX and iteration speed is key, it's a small price to pay.

But yeah, when you're building some kind of internal service with a classless CSS library that's just meant to provide some kind of dashboard to illustrate the state of your service, Go's stdlib is more than good enough and helps keep down long-term maintenance, everything will Just Keep Working.

I struggle to see where Rust fits in for web frameworks. You get the dependency tree explosion, the long compile times, difficulty collaborating with Frontend/UX. The benefit is, what, better performance? Is it really worth it?


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.


You know -- maybe I'm old, but please don't have the winds of industry change and make me learn another web framework lol.


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.


They don't, just flag them rather than reply them. It's the only way to be sure.


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. :)


chill brother, let an old man yell at clouds


hahah thanks :)


I'm old too, this is how I pretend to stay young.


Understandable. I wasn't criticizing your product -- best of luck and congrats on shipping :)


The youngest old man I know at any rate


[flagged]





Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: