Most active commenters
  • sodapopcan(3)
  • LeftHandPath(3)

←back to thread

319 points levkk | 28 comments | | HN request time: 0.41s | source | bottom

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/

1. kvirani ◴[] No.41914951[source]
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!

replies(4): >>41915143 #>>41915698 #>>41917900 #>>41917911 #
2. sodapopcan ◴[] No.41915143[source]
> * 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.

replies(1): >>41915585 #
3. jt2190 ◴[] No.41915585[source]
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”?
replies(1): >>41915940 #
4. ecshafer ◴[] No.41915698[source]
> 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.

replies(6): >>41916047 #>>41916627 #>>41916992 #>>41917222 #>>41917360 #>>41918421 #
5. sodapopcan ◴[] No.41915940{3}[source]
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.

replies(1): >>41916900 #
6. idle_zealot ◴[] No.41916047[source]
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.
7. yoyonamite ◴[] No.41916627[source]
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?
replies(2): >>41917125 #>>41917580 #
8. CSSer ◴[] No.41916900{4}[source]
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.
replies(1): >>41917068 #
9. JamesSwift ◴[] No.41916992[source]
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.

10. sodapopcan ◴[] No.41917068{5}[source]
I don’t really follow. My focus wasn’t on the naming but the location of responsibilities.
11. throwaway313373 ◴[] No.41917125{3}[source]
If I had to choose between thousands lines in models and thousands lines in controllers I'd definitely take "fat" models over "fat" controllers.
12. CharlieDigital ◴[] No.41917222[source]

    > 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

replies(1): >>41918998 #
13. JodieBenitez ◴[] No.41917360[source]
> 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.

replies(1): >>41917582 #
14. ecshafer ◴[] No.41917580{3}[source]
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.
15. fragmede ◴[] No.41917582{3}[source]
attach a debugger to the running process
16. LeftHandPath ◴[] No.41917900[source]
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.

replies(1): >>41918071 #
17. globular-toast ◴[] No.41917911[source]
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".
replies(1): >>41918206 #
18. freedomben ◴[] No.41918071[source]
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?
replies(2): >>41918135 #>>41919264 #
19. LeftHandPath ◴[] No.41918135{3}[source]
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.

replies(3): >>41918199 #>>41918443 #>>41918474 #
20. freedomben ◴[] No.41918199{4}[source]
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.
21. pdhborges ◴[] No.41918206[source]
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.

22. appguy ◴[] No.41918421[source]
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.
replies(1): >>41918538 #
23. snowAbstraction ◴[] No.41918443{4}[source]
Thanks for sharing that. AS400 always catches my eye after doing in internship at IBM, working with AS400 back in 2000.
24. ensignavenger ◴[] No.41918474{4}[source]
Are there not any Laravel shops that would take your money so you can "pay" for it?
replies(1): >>41918805 #
25. DanHulton ◴[] No.41918538{3}[source]
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.

26. LeftHandPath ◴[] No.41918805{5}[source]
Hah, I’m currently trying that tactic to get us on Bookstack for our SOPs. Never thought about doing it with Laravel. Could work!
27. sergiosgc ◴[] No.41918998{3}[source]
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.

28. unethical_ban ◴[] No.41919264{3}[source]
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.