adomokos / light-service

Series of Actions with an emphasis on simplicity.
MIT License
837 stars 67 forks source link

Can I chain Organizers together? #235

Closed founder3000 closed 2 years ago

founder3000 commented 2 years ago

Can an Organizer reduce another Organizer?

adomokos commented 2 years ago

Do you have an example of what that would look like?

founder3000 commented 2 years ago

As our app ingests information about products, there are a series of complicated steps that go on. The creation of a product in the system, with all the things it needs to be tied to, all happen in a specific order, and an organizer is the perfect place to have those steps delineated.

The ingestion process that consumes multiple products is itself complex, and the establishment of a single product is an iterative process, the last step in the collection process. So that's also an organizer.

I actually have this working. I just refer to the EstablishesProduct organizer as if it were an action. It receives the context just fine. The only thing I have to change, is that if a product is unable to be created for whatever reason, then a subsequent step that requires a product, I wrap that in a reduce_if like so:

reduce_if(->(ctx) { ctx.keys.include?(:product) }, [ Products::AddsBrand, # expects subscription_marketplace, brand, product ]),

I can't check for:

ctx.product

even if it's "there", because the organizer isn't an action, and therefor doesn't assign method names to things it "expects". But this way of checking the context works perfectly anyway.

I wanted to thank you very much for this gem. It has helped me to obliterate some god models and some convoluted code and really organize it in a readable way. I'm a big fan!

adomokos commented 2 years ago

I am unsure how I could advise you in this case. Maybe somebody else has a relevant recommendation.

Here is how we built up complex workflows, though:

  1. We never invoked another organizer from an action of a different organizer, as it could have led us to spaghetti code

  2. You can end an organizer with a context, and pass that context to another one. In fact, I remember we had actions like:

    actions = [
    DoSomethingAction,
    DoSomethingElseAction,
    PrepareContextForAnOrganizerAction,
    AnOrganizer.actions
    ].flatten

    This way we just "injected" the AnOrganizer's actions into another organizer's actions

  3. We used a "mapping" action (like PrepareContextForAnOrganizerAction) to map, alias, or prepare the context for the embedded actions of another organizer (AnOrganizer in this case).

Hth.

adomokos commented 2 years ago

I wanted to thank you very much for this gem. It has helped me to obliterate some god models and some convoluted code and really organize it in a readable way. I'm a big fan!

Thank you for saying "thank you". I don't use LightService much these days, as I have not been working with Ruby recently. But I always love to hear how the code I open-sourced helped others to clean up messy models and controllers. I always ask ppl to "pay me back" by spreading the word about LightService via conference talks, blog posts, etc.

founder3000 commented 2 years ago

I will absolutely do that my friend.

Your solution here is elegant and I hadn't thought of that. I've just added the ".actions" to the end like so:

def self.actions [ GetItems, iterate(:items, EstablishesItem.actions) ]

and that's working great! Thanks again.