My experience with Structured Concurrency
robert engels
rengels at ix.netcom.com
Sun Aug 17 22:19:40 UTC 2025
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 <mailto: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 <mailto: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 <mailto: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/25f06800/attachment-0001.htm>
More information about the loom-dev
mailing list