Opinionated vs modular web frameworks

web-frameworks

#1

Still remember a hot debate between Rails vs Merb, DHH vs Yehuda Katz?

Is this the same thing?


Elixir Blog Posts
#2

I don’t have any real experience of the Rails vs Merb discussion. I think a valid comparison is Sinatra vs Rails. Both of them are valuable parts of the Ruby ecosystem and I’m certainly not working to replace Phoenix.

I’ve tried to document my thinking as I went along. However if you have some concrete questions on Raxx vs Plug/merb/phoenix etc I will be happy to answer. (maybe in a new thread)


#3

If I remember correctly, the debate is centered around Rails’ opiniated monolithical approach vs Merb which was more modular and less opiniated. The same old debates between Django vs Flask, or Laravel vs Lumen/Silex. You go with the opiniated path (Omakase) or you go with minimum backpack in your shoulder.

But I don’t think that Phoenix is as opiniated as Rails. Sure Phoenix has some small magics especially on the MVC thing which glues the M, V and C by convention, but it’s not a big deal for me for the most case.

But I admit that there were times when I had several difficulties understanding Phoenix framework itself, notably the glue between the M, V and C. The new addition of “context” really helps me a lot on designing the MVC, because now I know the boundaries between my models and their interactions.

Phoenix itself has its advantage, because it has some CoC + ORM (Ecto) built in by default, it is arguably opiniated so many things are in the box for devs to use, so when I look at projects made by other person, I can easily learn what it does, because it can feel so familiar.

I had terrible experiences when using web micro frameworks using nodejs, when I looked at my friends’ nodejs projects (which don’t share any CoC as I do), I don’t even know what they were.

But still, it is just my own biased opinion. Many times as well, have I lost my way looking at Rails or even Phoenix projects.

“Opiniated” is a big word, and maybe controversial. When I learnt that Phoenix uses Brunch as its default frontend build tool management, I didn’t like it, so much, that whenever I generated a new Phoenix app, I always replaced it with Webpack, but one day I tried to learn about Brunch itself, and tried to like it. Later when I saw other Phoenix projects, which uses Brunch, there, I knew why opiniated things can sometimes help me. Honestly, this what makes Rails and other frameworks with strong CoC foundation popular and become the mainstream, because it has “convention”, something like gofmt and elixir syntax formatter, well, you like it or not, you have to do it, they are what the community agree.

Btw, I’m sorry admin, because my post is out of topic with this thread, maybe we can split this into another thread.


#4

The “batteries included framework” vs “compose your own components” discussion can be had about phoenix. Phoenix is definitely trying to be batteries included, and in therefore in my opinion it is quite a monolithic project.

That said this is not really an important discussion here. A batteries included monolithic framework could be built on top of Raxx, (I won’t build it but I would be intrigued to see what it might look like.)

Plug vs Raxx is the layer to compare. On the plug side client server communication is modelled by a conn (the connection). On the Raxx side it is message passing.
There are several things that are impossible to do in plug in a pure fashion. I found that unsatisfying for a functional programming language. It also lead to some specific bugs in my code as well. However if functional purity is not something you value as much then you might well find it hard to separate the philosophies and from a practical standpoint plug clearly has the larger ecosystem. We are catching up though :slight_smile:

I gave this talk which has more details about the bugs I ran into with plug. as well as some of my opinions on monolithic frameworks http://confreaks.tv/videos/elixirdaze2018-purify-your-web-development-with-raxx


#5

I think “batteries included” is indeed a very good description to phoenix! Since it’s very easy to just switch the batteries, or even remove some of them and it still works. :slight_smile:

In the other hand, I can’t think of Rails as “batteries included”, it’s more of a “batteries welded” or something like that. You can try to remove them, but you will have some hard time doing that. :joy:


#6

Elixir and Erlang themselves are not pure functional programming languages, because Erlang itself was built, not in academic world but in business world. There were probably trade-offs made by Erlang devs, in order to solve some business problems they faced.

Some devs face problems like developers’ experiences and familiarities. Especially if they are in tight deadlines and schedules, a quick MVP needs to be built (deployed, presented and marketed) in order for their business to run and collect feedbacks immediately. This is why Laravel framework is so huge and popular. Of course, there are many things (in Phoenix, Laravel or other opiniated frameworks) that are not considered to be good/proper by some experts.

But if I need to choose a framework to use (working together with some of my co-workers) for quick MVP and especially for long term concerns, Phoenix is definitely a better option.

Well, to be honest, even GenServer, Task, Agent, Supervisor and Registry themselves are arguably opiniated IMHO. Why not just use bare processes? Well, it’s because we want to do many things based on agreed conventions.


#7

This bold (important) statement seams unqualified in terms of use-case and alternatives. Maybe I misunderstood some context, just wanted to check, are you saying: Phoenix is definitely a better option compared to all alternatives and for any conceivable project? You mention both MVP and long term concerns


#8

I think yes.

For quick MVP things to do, Phoenix got the conventions and generators (optional), lots of other stuffs built in, including the Exunit, templating, Ecto and i18n as well.

Long term because those people with similar stack experiences (Ecto, Exunit, etc) can join immediately. The addition of “contexts” can hopefully guide the developers to make clearer and more understandable code (models and their interactions), which is nice.


#9

I think it all depends on what do you mean by “good”, “better” or “best” terms. What is your perspective? If you look from business perspective, and need MVP as fast as possible, then the tool (framework) that let you build applications fastest way, and let many people just jump in and start coding, is really the best tool.

But then again… Rails may be better, or XXX framework, because there are not really many people using Elixir and Phoenix compared to Rails or many other frameworks.

If by “best” you look from perspective of code maintenance, purity of implementation, ease of testing and code (implementation) readability, and all of those things that business side probably does not care at all… then, well I think there are better languages/frameworks than Elixir/Phoenix.

But if you want to combine the speed of code delivery with easy of maintenance and readability, then you need to sacrifice some of advantages from either side. And because of this trade-offs, from one side Elixir/Phoenix is really great combo (and is actually my tool of choice for web related stuff i do at work), on the other hand we can’t really say about any framework being “best”, there are many “good” solutions and if one is “better” than the other depends heavily on context.

When I was reading first about Raxx I though, great… it’s rack-alike, so it probably sucks hard, like IMHO rack does (but I really know only weak sides of rack, and simply discredit any good there may be :see_no_evil: ). But after watching it for a while, i really, really dig the idea. I love how it’s simple and clean to stream data in Raxx, it’s the “best” :wink: way to handle it I’ve seen so far.

So TL;DR “BEST” Really depends on context.


#10

This is always a fantastic point. Erlang is pure functional (if pretending that the proc-dict is an algebraic effect) per-actor, the messages are what introduces impurity in the form of how it is in the real-world too. :slight_smile:

This makes it easy to reason about any single specific actor, so you only have to watch the messages. :slight_smile:


#11

Phoenix takes care of a few little things that are quite annoying to deal with otherwise. Specifically around templating - it handles sanitisation, CSRF-tokens, embedded templates, link helpers, form helpers etc in a way that would be a pain to do yourself.


#12

Phoenix is certainly modular and flexible, no argument there. It is also opinionated about its conventions. This allows easy on-boarding through the use of generators and sensible defaults. However, there’s no denying the wide variety of solutions and approaches we see in blogs, books and tutorials. This flexibility has pushed me to expand my understanding of the mechanics of a web server more than any other framework I have encountered.


#13

Do you use these much. The people working on our front end have pretty much stopped using form helpers. Also as a change to the API is a breaking change a hard coded url is also not much of a hardship


#14

Except that side-effects like printing IO are accessible to you at any time. (And that’s not really a bad thing – it makes debugging a whole lot easier; in Haskell there are weird ‘hacks’ like UnsafePerformIO for exactly this reason – ). And I guess that ETS also breaks the ‘immutability’-world a little bit? (Of course, it, just like the process-dictionary, are mainly to be used as optimizations rather than initial go-to tools).

Still, I completely agree with you that the reasoning within a process is very easy because of the large amount of referential transparency, and it’s not that difficult to think about any other processes as being black boxes that might send you messages or you might send them messages at any given time.


#15

Having very modular software sounds pretty good on paper but we all do that for money and thus saving time is essential. Having a convention over configuration in my eyes undoubtedly saves time.

IMO that discussion is impossible without including programmer productivity. I can agree that a statically and strongly typed language can rid you of a class of bugs from the get go, but is it really quicker and less tedious coding in the said language? That’s an interesting discussion IMO and it already has been had on this forum several times, sadly without a definitive conclusion. :confused:

I feel both the business and part of the programmers very easily hand-wave human happiness away while working – “it’s a job”, “put on your big boy pants”, “job is hard, deal with it” and a whole other class of such BS – and that’s a huge mistake; I lost count on the amount of times I’ve seen VERY talented programmers leave because nobody addressed 1-2 pretty small work condition circumstances which could have been easily addressed but nobody cared because it didn’t make the organization money (it wouldn’t lose them money either – management simply didn’t care). Needless to say, the org struggled for months afterwards.

Not demeaning Raxx (I liked it) but have in mind that not everybody is fully on board with the whole “100% JS frontend” thing. There are many sites that simply work better if they are server-side rendered, f.ex. governmental agencies open data portals. Being server-side rendered also helps organizations scrape their data without the agency investing in API gateways (seen that shameful practice probably 50 times in my life so far). But let’s not hang on to one example, there really are multitude of cases when JS is simply superfluous and is many times shoehorned without question.

As you yourself said, this isn’t Phoenix vs. Raxx – they serve different purposes. I am in agreement with you about the drawbacks of Plug but so far didn’t have problems. Can you please point out examples where Plug is not working well?


#16

I have been pointing people to this talk I gave. http://crowdhailer.me/2018-03-02/raxx-refined-web-development/

Specific issues are around message signing and streaming responses. They are shown in first 10 min of talk


#17

Appreciate the video link and I’ll watch it, but have you considered also extracting some good rationale for using Raxx point by point in a blog post?


#18

There is some stuff in a variety of places. also this blog post http://crowdhailer.me/2018-02-23/http-is-message-passing/

But before 1.0 I’m not wanting to spend too much time on it because the example code might not be relevant for long


#19

Programmers know the benefit of everthing and the tradeoffs of nothing.

Again it’s a tradeoff. Historically “convention over configuration” became popular and made sense when “frameworks” were largely monolithic and attempted to stay relevant by absorbing more and more capabilities over time which then needed to be configured to be adapted to the particular use case. However the micromanagement/configuration needed on the lowest level of granularity was getting ridiculous. A “flexible” framework required complex configuration that often wasn’t justified by the exposed capabilities of the finished product.

The Rails model succeeded because there was a large enough market for “web application products” that could be implemented via the CRUD model with web pages essentially projecting the contents of the database tables (which in itself was a tradeoff against known design guidelines like “User interfaces that conform to implementation models are bad” (Alan Cooper, p.31, About Face, 1995)). The established conventions worked for certain products which shared significant commonalities allowing these “products” to have a relatively short time-to-initial-success. But the tradeoff of this approach was architecture - Rails applications had the same structure regardless of what purpose/domain they served. To a certain extent this approach also made developers more fungible because most Rails applications looked the same.

But one also has to acknowledge that there still is the upfront cost of learning the conventions when implementing the first product (which can be more difficult when most of the “connections” are implicit) - a cost which can only amortized if a) you are going to use the framework again and b) the next product is sufficiently similar to the first. And the more one’s product diverges from the “framework’s norm” the less benefit one ends up getting - or in the worst case one ends up shoe-horning the framework onto the problem resulting in a poorly implemented product. Software developer’s need to be careful to not to become #{name_of_framework} developers but instead should aim to become Generalizing Specialists, i.e. work on T-shaped skills. So as such it isn’t sufficient to merely learn how to use a framework but also to understand how and why it works because that knowledge is necessary to assess whether or not a framework (with it’s conventions) is (and continues to be) a good fit for a particular problem. At the core effective software developers are problem solvers - not mere tool wielders.

As a framework Phoenix sets a good standard. It’s default install provides a reasonable base that lets one code around the edges to get more familiar with one’s surroundings while at the same time allowing one to opt-out of certain aspects (asset management, templates and Ecto/Postgres). (Some more figures like Phoenix in Action; A Little Phoenix Overview; Figure 3.9. The final step: the Template illustrating how things fit together would be welcome). But the scope of applications for exposing domain functionality over the web keeps expanding.

“Assemble your own microframework” serves a different market. Microservices have brought the notion of “the right boundaries” to the forefront (the notion always existed but typically in the service of reusablty rather than replaceability). There are times where existing frameworks come with too much baggage or simply make assumptions that lead to poorly matched abstractions for certain problem domains. This is where having access to small libraries, each with a highly focused responsibility (“does one and only one thing well”) and easily integrateable interfaces comes in handy because it makes it possible to “assemble your own microframework”. Yes, there will be “boilerplate” that is necessary to “glue” these parts together into an integrated whole - but that isn’t unusual for a custom solution.

Is one approach superior to the other? It depends. In many cases the “business case” is so ill defined that you don’t have enough information to make an educated choice on what your microframework needs to look like (though it could be argued that it is possible to adapt as discoveries are made). In that case using an existing framework (with its conventions) helps to get you moving in order to get needed feedback as early as possible. Going forward the product may never suffer from the tradeoffs that the framework invariably makes. But it is necessary to know which tradeoffs the framework makes. Once it’s clear that the framework is a potential liability it may be time to move to your own microframework. If you are going to change, you want to change early. Once you have your own microframework there is the challenge of managing coupling carefully so parts can be replaced as needed while avoiding getting into “rewrite territory”.

There is no one size fits all. Being able to effectively use a framework (and it conventions) is good. Being also able to piece one together and keep it aligned with the problem domain is better (but usually more difficult).


#20

Fully agreed. Rails arrived at the right place, at the right time, and made quite a dent on the industry. Whether it was for the better or the worse is an open debate still but your generalization is on point regardless.

SIDE NOTE:

I spent anywhere between 6 to 7 years doing Rails. Looking back to it now, the Rails CoC approach actually was a very poor fit for most apps as they matured. It served the apps very adequately at the start and only diverged farther and farther away as the time passed (and features were added).

I’m pretty extremely opinionated on Rails these days; I write it off as “a near-perfect tool for MVPs and strongly specialized internal systems with small load where not having well-defined contract can be fought with quickly”. Anything beyond these boundaries is begging for trouble if you use Rails.

I respectfully disagree that this is a problem in practical reality. Switching company, or team, or the product you work on, always has onboarding costs (sometimes even switching tickets has these costs as well). You simply switch around what kind of onboarding costs you pay but you cannot remove them.

(Allow me to shift the topic to Elixir specifically because we can never have concrete arguments if we speak in general.)

I also disagree that Elixir and co. shoehorn you into that many conventions. Nowadays I make umbrella apps that manage very different aspects in the project and only one app actually has a clue that the data below is in Postgres, or MySQL / MariaDB, or MSSQL, or whatever. Everything else has to use MyProject.Order.last_for_user(user, count). I have a separate app that manages logging and/or monitoring. Another one that handles reports, etc. to infinity. The only things Elixir “forces” you to do is to be a good FP developer and to know Elixir’s specifics – which aren’t that many. Same for Phoenix: 10 mins reading the tutorial will explain its very minimal boilerplate quite well.

I don’t feel Elixir in general is skewing your view of the reality of your required product’s structure, and it doesn’t make you sacrifice clarity, nor does it make you abandon the very productive convention that your code must be modeled after your data. Sky is the limit for your imagination. I was much more constrained in all other frameworks I used before (especially Rails).

I am with you. But then again, we have Plug (and now Raxx) if you don’t want to be bound by Phoenix’s conventions which are arguably lower-level and allow people to assemble their own frameworks. We can’t question absolutely everything up to its very core; we do not do science or philosophy for a living. We do coding for our living. Business realities say we have to be efficient. Having some assumptions and conventions (minimal or huge) is a key to that.

Eh, no language is immune to rewriting hell, even Elixir. We are all humans and we can and will model an app poorly – also because as you adequately pointed out, many times business requirements change a lot in small timeframes – and then one day we have to correct it or else the tech debt will make adding even small features very long and tedious, if not impossible. I don’t think we can prevent the expense of rewriting. We can only delay it for that much longer. Or pass it down to the next team.

I find that the lack of imagination and culture are the main problems here, not technology. I realized that I constrained myself due to the frameworks I worked with and once that became crystal clear, it only took me months to throw away those shackles and learn to create code that follows the data model and eventually make a bridge between that and the frontend tech you have to use (HTTP + Javascript in most of the cases). And that’s not related ot Elixir at all; you can write pretty excellent and well-behaved code in Javascript and PHP – but I found that FP in general guides you to the more constructive coding habits so a part of the mental friction is removed. That doesn’t mean there aren’t excellent devs in C, Javascript, PHP or COBOL. There are.

(Your replies are, as always, very informative and cultured and I thank you for educating the community!)