Closed dustineichler closed 2 years ago
Please expand on the pattern you are referring to (it sounds like you are suggesting to use something other than bundler
or mixing up some unrelated concepts).
I'm talking about how we distribute extensions through solidusio
or solidusio-contrib
for one and two how extensions are packaged as a Rails::Engine
.
How this would ultimately work... Unclear, but I've been looking at:
How this would ultimately work... Unclear
This doesn't help much and doesn't really make your case about extensions being "an idea whose time has past" or how it would help with maintainability.
Extensions are distributed as gems which bundler pulls from whatever location you specify (local path, github, rubygems, etc.). So, if you don't go through solidusio
or solidusio-contrib
, then you're suggesting to replace bundler with something else?
I'm pretty sure you're mixing up some concepts here. There is nothing preventing you to not use Rails::Engine
, but you will have to require manually your files and do yourself all the steps that engines do take care of: initialization hooks, generators (migrations and such), assets, locales, etc.
All in all, this doesn't make a lot of sense. Is pulling some extensions into the main project (like Rails did with ActiveStorage for example) would be something that fits with what you're trying to say/expose? If not, then please give some details about the problem(s) you're trying to solve, because you're proposing a change with an unknown solution to an unknown/non-existant problem otherwise.
There is nothing preventing you to not use
Rails::Engine
, but you will have to require manually your files and do yourself all the steps that engines do take care of: initialization hooks, generators (migrations and such), assets, locales, etc.
All true, but the tool for generating new extensions makes them engines by default, and I agree with @dustineichler that this is not a good pattern to encourage.
I've tried to follow what I think are a better set of practices for building extensions on recent stuff: normal gems (not engines), no class_eval
/prepend
, etc.
I personally think encouraging building them like that, and potentially moving some extensions over to something more like that would be great.
@jarednorman, thanks for your input and clarifying what was the intention of the issue (if this is really what it was about), because I thought it was way more intrusive/impactful than that. I can live with a different generator for extensions which isn't integrated with Rails and ignore all the conventions, I use a different one anyway.
Right now, the only thing I think is wrong (not ideal) with the extensions is the pattern already discussed here. Otherwise, I don't see how Rails::Engine
and class_eval/prepend
are problematic (you seem to be aware of something that I am not).
I'm very surprised that you don't see an issue with class_eval
/prepend
. Maintaining a system of dozens of extensions where all of them can modify the behaviour of each other has been both a burden on maintainers, as well has caused many bugs.
It essentially locks even private class APIs in place because extensions may be relying on them. In talking with our previous full time maintainer, he considered it one of the more painful pieces of maintaining the project, because every store has either class_eval
ed some random things, or has extensions that do it, and if anyone moves logic around in core classes they're liable to just randomly break a bunch of stuff.
I see our adoption of class_eval
/prepend
is a significant burden when it comes to stores keeping up with latest releases, and with maintenance in general. It's certainly been my experience that well-defined extension points offer a much better experience for both store and project maintenance, and that seemed to be the consensus during the group discussions at Southeast Solidus, but I'm curious how other people feel.
I see your point. Although, the problems you brought up aren't specific to class_eval
, but to an ignored/overlooked principle of writing extensible code: the open-closed principle (should be open for extension, but closed for modification).
So, for this instance, this means that you should use class_eval
to modify behavior by providing some hooks and/or a public interface to implement if modifications are to be expected/encouraged and modifying/using private parts should be discouraged by being explicit about it: meaning using the private
keyword and/or prefixing with an underscore _
(which would be prefered if class_eval
is expected to be used). The usage you describe is a misused of class_eval
. It's very easy to blame the knife when you cut yourself, but unless you evaluate the context, you might be blaming the wrong thing.
Basically, I don't see an issue with this because the cause of headache isn't class_eval
per se: you'll have the same problems if you encapsulate the behavior in another class and delegate. What I mean by this is that unless you architecture your class in an extensible way, then the same problems will arise. Not only that, but you might not have other choices, but to reopen the class or call send
because the encapsulation isn't the right one. Sometimes class_eval
is the right call, sometimes it's not, there are a whole bunch of ways to address the same problem. I'm guessing we see class_eval
in Spree/Solidus often because more often than not we need to add a table related to other existing tables and it would imply to add relations between existing models. The easiest way to do this is by reopening the model and adding has_many
, has_one
, belongs_to
and whatnot and it doesn't violate the open-closed principle.
Regardless of all I've just said, if you really want to make your extension maintenance bulletproof and independant of how extensions are implemented by others, not not using class_eval
isn't the most robust way, because it's a losing battle to impose your way to do things. What we do with our overrides regardless of using or not class_eval
, ìnclude
, prepend
, is we have a base test helper which compare a snapshot of the file(s) we modified when we made the modifications and run a diff everytime we update the gems. This will tell you what has changed and if the modifications should be addressed.
@patleb We should take a step back a here, as we've gone way off into the weeds. There's a huge disconnect happening between us in this conversation: @dustineichler was trying to continue a specific discussion from Southeast Solidus, but unfortunately didn't provide any real context for those who missed out on that conversation.
I totally appreciate that you're largely happy with how the extensions currently work and as such might be worried about unnecessary changes. Let's try to keep the discussion constructive and focus on ideas that have been explicitly suggested and avoid speculating too much. No one is suggesting replacing bundler, or banning the use of class_eval
.
For a little more context: at the conference there was a discussion where some people expressed pain with how we currently handle extensions, and we discussed where we thought the trouble was coming from, and how we might alleviate it.
I think the goal here should be to continue the extension conversation from Southeast Solidus and hear from more people on where they are having issues and brainstorm on how we can address those issues.
@jarednorman, you said that you were surprised that I don't see an issue with class_eval
and that you were curious how other people feel... so, naturally, I expressed my point of view and gave some actionable tips. Which, as I understand now, were unsolicited and you actually meant that I was wrong to not see an issue regardless of whatever and you wanted to hear people from Southeast Solidus specifically.
You have to understand that I have an obligation (which means I would rather not) to check from time to time for issues that would impact our clients in the long run. So, if there is an issue with the title "RFC: Replace Solidus Extensions with new library package and distribution pattern", I will most certainly associate risk and money to this kind of change and I have to inform myself about what the issue actually is. So, it's a little unfortunate that issues are now used as a forum.
Also, please, if the discussion is about how extensions are architectured/configured, then use these terms in the description of the issue instead of "library package" which would mean "gems" and "distribution pattern" which would mean "bundler".
Let's try to keep the discussion constructive
Could you please expand on the non-constructive part(s) of what I wrote? I don't get it.
and focus on ideas that have been explicitly suggested and avoid speculating too much
Sorry, but there was nothing explicit about what was written in this issue, that's why I jumped in.
No one is suggesting replacing bundler
My questions weren't answered about the distribution part... so, I guess you're answering my question about bundler with this.
or banning the use of
class_eval
You specifically said "I see our adoption of class_eval
/prepend
is a significant burden" and I was merely suggesting that it might not be the cause of the burden with an actual explanation, a scenario and alternatives.
Alright, I'm out. Have a good one!
Yep, all totally understandable, and I didn't mean to imply any bad faith when I suggested we keep it constructive, only that we'd got way off track from the original/intended discussion (which is fair due to the lack of explanation/context.) Have a good rest of your weekend! 😄
What does a solidus extension built with Rails::Engine
give us that we couldn't easily replace with a standalone or simplified Ruby gem? Namespace isolation? Can't really think of anything else off the top of my head. Engine initialization hooks can be replaced with Rails::Railtie
. Also worth investigating: Classboxes.
I agree we should change the title of this issue and add some more context to its description. I wasn't at the conference as well and it was quite hard to understand what we were talking about, and I still have some doubts, so I'm sorry if I'm not getting it yet.
What I'd like to point out is that the current pattern used for extensions is good if we need to inject routes or other Rails layers resources (models, views, controllers, migrations, assets) into our stores.
The extension @jarednorman built is a great example of adding some business logic to a store and it is made possible because of the presence of an extension point for that particular feature into core and because the extension does not need any assets, routes, controllers or migrations to be used.
The point, I think, is not to find an alternative when we need those things into an extension, because using what Rails gives us and maintains for us is the best and more responsible way of solving that problem (or I can't see any valid alternative yet). The point is trying to only build extensions that provide only business logic, leaving to each developer the possibility/responsibility of building the rest of the things into their store on their own.
For me, this opens some questions:
class_eval
/prepend
to quick fix their issues instead of submitting PRs that could be opinionated and requires some time to be merged and released in the next Solidus version.@kennyadsl good points. should update. suggestion on title change? might be worth closing and moving to solidus_cmd
too.
Integrating my extension involved adding some views/routes/controllers/models for one of the stores that I integrated it with. I very intentionally didn't provide certain things out of the box: for example, I didn't include the state machine transitions and jobs to do transaction reporting because not all the stores I'm using it with need that functionality, and doing it properly would require assuming the use of active_job
.
Instead I've provided the API required to do the reporting, and am going to document how to integrate that with your store, including making clear some of the things that may be different from store to store.
On top of this, I'm considering providing the extra admin functionality as its own extension, as well as active_job
based transaction reporting as another.
This is all to say that the core functionality for some extensions might belong in its own package, and the state machine transitions, routes, controllers, views, models, etc. might belong in another package for those stores that don't need anything too custom and want a "works out of the box" experience can have that as well. Using this approach doesn't change how anything works for those currently using existing extensions, but might provide some extra flexibility for the stores that want it.
@dustineichler
suggestion on title change?
[RFC] Should the new extensions template create a Rails Engine?
What about something like this?
might be worth closing and moving to
solidus_cmd
too.
I think we can discuss here since it has more visibility and then eventually create PRs on the solidus_cmd
repository referring to this PR.
@jarednorman
Sure, I was not criticizing this approach, and it's clear to me that this was intentional. Providing good documentation is a great way to delegate to each store for the rest of the implementation.
I was thinking that it could be a good idea to provide 2 commands (or an option) to create a new extension with solidus_cmd
but maybe it's not worth it since the extension without the Rails Engine skeleton would be very similar to a new gem created with Bundler with bundle gem new_gem
, WDYT?
@kennyadsl i like it. building up solidus cli has many benefits. not just here imo. i also think it's a good idea to expand-provide employable patterns beyond what's initially generated. lastly, providing a boiler plate is a good idea. 1. helps set extension expectation 2. ensure minimum quality. 3. reduce setup time.
One idea that I presented at the conference was remembering the idea of Apple migrating from MacOS to OsX. They had the concept of Yellow Box which was the new OSX interface and BlueBox the old interface.
I spoke about this in Memphis, and how we could use this paradigm for a migration in model. We could have the current extensions and label them "Blue" and the new model based on a much smarter plugin model and then give extension authors 12-18 months to come into compliance with the new extension model.
A few challenges: 1.) Designing or adapting a good plugin model 2.) Evangelizing to the community this is a good idea 3.) Providing support,documentation to plugin authors
But I think its worth it! And I think we can serve the community better by making the plugin migration a curated experience vs a bit of a guessing game. Naming our intentions and providing clear guidelines will go along way!
-dh
On Fri, Mar 1, 2019 at 4:14 AM Alberto Vena notifications@github.com wrote:
@dustineichler https://github.com/dustineichler
suggestion on title change?
[RFC] Should the new extensions template create a Rails Engine?
What about something like this?
might be worth closing and moving to solidus_cmd too.
I think we can discuss here since it has more visibility and then eventually create PRs on the solidus_cmd repository referring to this PR.
@jarednorman https://github.com/jarednorman
Sure, I was not criticizing this approach, and it's clear to me that this was intentional. Providing good documentation is a great way to delegate to each store for the rest of the implementation.
I was thinking that it could be a good idea to provide 2 commands (or an option) to create a new extension with solidus_cmd but maybe it's not worth it since the extension without the Rails Engine skeleton would be very similar to a new gem created with Bundler with bundle gem new_gem, WDYT?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/solidusio/solidus/issues/3120#issuecomment-468597555, or mute the thread https://github.com/notifications/unsubscribe-auth/AAOZrxmK6IkXygyxIM-q8y2nvHgRh5Otks5vSO-DgaJpZM4bJ5-j .
About building extensions that provide only business logic, not sure how to avoid monkey patching without a configurable class or preference.
Maybe we can start evangelizing the community about avoid class_eval
for monkey patching when posible, and request feedback from extensions developers about more configurable entry points that are necessary.
Aside, not sure that we need solidus_cmd
. rails
already has a command to generate engines (rails plugin new
), and allow us to use custom templates too. And we can use bundler
for generate a regular gem.
Alternatively, it's not unreasonable to have solidus_cmd
do a little more, like set up the test suite, require in the solidus factories, provide a default CI config with up to date version matrix, etc.
Just a thought.
Sure, any improvement is welcome. As far as I can see right now:
If there are other concerns or missing things, let's discuss and fix them!
Solidus extensions is an idea whose time has past. Replacing extensions with a new pattern was discussed at length during south east solidus this year. I'd like to explore packaging and distribution without
Rails::Engine
as a requirement. In terms of maintainability, I believe this would simplify things immensely.Questions, comments and concerns below.