My experience with Structured Concurrency
David Alayachew
davidalayachew at gmail.com
Mon Aug 18 00:28:01 UTC 2025
Hah, so my problem isn't an API problem, it's just a basic OOP/DRY basics
problem.
Yeah, I think I see what you are saying. And what needs to be done.
Ever since Java 8, interfaces have become more and more powerful that I
just don't use abstract classes anymore. The interface alone has been good
enough for most of my uses cases. I haven't written an abstract class in
over a year now, for example.
But yeah, you are right -- I've just been using the wrong tool for the job
here. A couple abstract class with a few utility methods (and maybe an
empty list to store subtasks) would have turned this problem into a mild
inconvenience at best.
Lol, I'm still going to be spoiled and say I'd like the factory method
anyways. But yes, I think the API as is is sufficient for making the
necessary joiners I need.
Thanks.
On Sun, Aug 17, 2025, 6:19 PM robert engels <rengels at ix.netcom.com> wrote:
> Exactly, in my experience if you are creating lots of classes with slight
> differences you are not only missing an opportunity for DRY, but you are
> increasing the cognitive load substantially - i.e. understanding the code
> base (without lots of jumping around).
>
> I haven’t dug deep into your use case, but it sounds like an
> XXXErrorHandler(s) based anonymous class(es) with properly defined
> extensions/callbacks would allow a code reader at the site to see the
> specialized handling these requests take - rather than trying to name all
> of the scenarios and then have to understand them and remember what all of
> them mean.
>
> It does make testing a bit different - but I may be an outlier here - and
> I think more black box testing is beneficial anyway - rather than trying to
> test each handling implementation (if the interfaces/bases are properly
> designed, the extension code should be trivial and hard to mess up).
>
> On Aug 17, 2025, at 4:46 PM, David Alayachew <davidalayachew at gmail.com>
> wrote:
>
> Can you give an example? I used Anonymous classes in my solutions, but
> most of them were just based off of the Joiner interface.
>
> I guess I could introduce some state into the abstract class, and then
> just tweak it for my needs. And obviously, I'd need a new abstract class
> for each "category" of joiner. Then from there, just implement it inline,
> making each inline case cheaper. Is that what you are hinting to?
>
> On Sun, Aug 17, 2025, 5:28 PM robert engels <rengels at ix.netcom.com> wrote:
>
>> Isn’t this a perfect use of an anonymous class with a base class?
>>
>> On Aug 17, 2025, at 4:00 PM, David Alayachew <davidalayachew at gmail.com>
>> 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 <alan.bateman at oracle.com>
>> 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
>>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250817/8ea5c027/attachment.htm>
More information about the loom-dev
mailing list