Hacker News new | ask | show | jobs
by Draiken 818 days ago
I wrote a whole blog post on this because of the amount of harm caused by these types of systems. Interactors, interactions, pipelines or whatever other name, can very easily ruin a codebase in my experience. Already happened multiple times in my career.

Developers don't like to think about abstractions and naming is a known hard problem. People pick this up and now everything is easy. Every name becomes: DoThis, DoThat, DoThisOtherThing and only answers to `call`. No need to think!

Now you have deeply nested procedures that oversimplify their interfaces to match the library and suddenly you no longer have meaningful error types, you have strings. You no longer have abstractions, you only have procedural code with some objects in the middle. The list goes on.

Like most things in development, used in the right places and with careful consideration, this can be very valuable. But this is a very insidious change that turns your code into a functional style with several drawbacks that are rarely considered.

2 comments

I agree with the drawbacks listed here. I would add that "careful consideration" of any pattern we use is the job description.

I would further add that we should extend that thoughtfulness to the opinionated frameworks many of us rely on. They usually come with hundreds of complicated patterns baked in, and we "oversimplify our interfaces" to match the framework as a matter of course. All I'm saying is that committing to any mental model provided by a design pattern or framework has similar drawbacks. In this case, the mental model is "a big operation is a list of smaller operations in a chain". Use with caution.

> No need to think!

Not sure I agree with fully. If anything, I've found that I need to think harder about what constitutes a step in a workflow, what are the names of each stage, what concept they encapsulate. I can't just chuck everything into a god object or a deeply nested hierarchy tree somewhere.

But again I agree. Using this pattern I've definitely over-complicated or gone down the wrong path at times. I would say though that I found it easier to roll back and change direction when compared deeply nested object graphs, for example.

> Developers don't like to think about abstractions and naming is a known hard problem. People pick this up and now everything is easy. Every name becomes: DoThis, DoThat, DoThisOtherThing and only answers to `call`. No need to think!

This 'framework' doesn't require that though:

    def double_number(r) = r.continue(r.value * 2)
    def add_one(r) = r.continue(r.value + 1)
    def square_number(r) = r.continue(r.value ** 2)

    pipeline = Pipeline.new do |pl|
      pl.step method(:double_number)
      pl.step method(:add_one)
      pl.step method(:square_number)
    end
Just methods, with no need to implement call() because that is what a method/proc/lambda in ruby implements. In other languages like C# you could have an API that can take a Func<Result, Result> so that you can just pass delgates/lambdas/methods to it. And adding a bit more DSL could make the construction of the pipeline even less verbose.

You can use this without going full-Java and creating a dozen (or a hundred) 5-line classes in different files.