Isn’t this a perfect use of an anonymous class with a base class?
> On Aug 17, 2025, at 4:00 PM, David Alayachew <[email protected]> wrote:
>
> > I will guess that #1, #2, and #5 are relatively
> > simpler Joiner implementations, is there really
> > any benefit to use composition or inheritance here?
>
> Sorry, I have been unclear. Let me clarify.
>
> Yes, it is not hard at all to implement #1, #2, and #5 at all. But I don't
> have 5 joiners. I have well past 30 of them.
>
> Most of my joiners are very similar to each other. The 5 I showed you are
> most of the major "categories" of joiners I would make. But each category
> would have many derivatives of it.
>
> For example, #2 had a derivative that was the same, but for multiple
> Exception types as opposed to just one. Another derivative of #2 would go
> past a type test, and actually look at the fields of the Exception (like HTTP
> Error Code). Yet another would go one level deep, in case the exception was
> wrapped (like wrapping an HTTP Exception in a RuntimeException).
>
> So I started making a whole new class each time I wanted to do almost the
> same thing. But you start to run out of names that accurately describe what
> you are doing. And yeah, some joiners are going to be heavily reused, so it
> makes sense for them to have a whole type (and maybe source file). But many
> won't either.
>
> Once I realized that I was making a bunch of the same thing with minor
> variations, that's when I started thinking about inheritance and composition.
> Sorry if I made it sound like that's what I first jumped to. No, I thought
> about inheritance and composition because those are usually the default
> answers to the question of "How do I do what T is doing, but with a minor
> variation?"
>
> But inheritance and composition didn't get me very far for these joiners,
> which is what I was trying to say in my original email. Inheritance with
> state is error-prone (from my experience). And composition meant that I was
> making my code brittle. What if those methods I am depending upon need to
> change?
>
> So I went back to making each joiner be its own thing. In reality, most of my
> custom Joiners were either a simple record implementing the Joiner, or an
> anonymous class. Records are fine, but you start to run out of reasonable
> names when you have 5 different records that do close to the same thing. I
> kind of found a compromise by creating the record Joiner inside the method
> itself that I am working in (or the class if other methods need it too). That
> way, it's scoped off from the rest of the world. But considering how many I
> was making, it felt like a clunky solution. I'm fine peppering my code base
> with inlined records all over the place *as long as those records don't have
> a body*. But once they do, it starts to get annoying, and makes the code
> harder to read and skim.
>
> From there, I thought about making a factory. You know the rest of the story.
>
> > For #4 and #5 then its surprising that there is RPC
> > or split/join in the onComplete method. The
> > onComplete method is called with the completed
> > subtask and any exception/error executing
> > onComplete isn't going to change the subtask
> > status. Is there a reason you've chosen to put
> > that code there rather than in the subtasks?
>
> Yeah. Long story short, if that RPC call or the nested scope fails, well the
> literal goal that I created this scope to do (contruct an object) has, in
> effect, failed. That's grounds to just throw an exception and see if someone
> upstream can handle it. Maybe a retry or something.
>
> To me, it felt like I was keeping inline with what onComplete was trying to
> do -- sort of be an AOP-like post-processing joinpoint. If the contents of
> onComplete fails, well then the goal was unattainable anyways, so killing the
> scope via thrown exception doesn't feel wrong. And the exception will
> propagate, so it felt like I was following right along with how the spec
> intended things to go. Granted, I certainly am marching on the edge here,
> I'll concede that.
>
> > (for his API then the question as to "where" to
> > put code is a good discussion as it may not be
> > always obvious whether to code should execute in
> > the subtask, in the Joiner handling subtask
> > completion, or in the main task in the processing
> > after join.
>
> I would love a short guide on what code to put in what place. This looks to
> be a pretty integral API for handling a large number of tasks moving forward,
> so I see value in it.
>
>
> On Sun, Aug 17, 2025 at 1:13 PM Alan Bateman <[email protected]
> <mailto:[email protected]>> wrote:
>> On 16/08/2025 20:23, David Alayachew wrote:
>>> :
>>>
>>> Sure. Let me highlight 5 of them. Let me know if you need more examples --
>>> I have about 30+ custom implementations.
>>
>> Thanks for sharing this selection.
>>
>> I will guess that #1, #2, and #5 are relatively simpler Joiner
>> implementations, is there really any benefit to use composition or
>> inheritance here?
>>
>> For #4 and #5 then its surprising that there is RPC or split/join in the
>> onComplete method. The onComplete method is called with the completed
>> subtask and any exception/error executing onComplete isn't going to change
>> the subtask status. Is there a reason you've chosen to put that code there
>> rather than in the subtasks? (for his API then the question as to "where" to
>> put code is a good discussion as it may not be always obvious whether to
>> code should execute in the subtask, in the Joiner handling subtask
>> completion, or in the main task in the processing after join.
>>
>> -Alan