Hi Alex,

Got it - with this suggestion, we will have to add this information manually 
into the Delegators (which are where we are emitting the events). Personally, 
this felt like a lot of repeated code for me that would be mixed in with the 
important parts of crafting the event and I was hoping not to crowd that logic 
- which is why I had pushed it out to the Event Listeners to take care of this 
there. This way, we were keeping the Java records crisp and not having to add 
the copy-paste logic into the event during event emission time.

I fully agree with the philosophy that id, timestamp, and user information 
should be part of a PolarisEvent. But I still don’t think this suggested change 
is a great way to go, even though it may be the best option we have at the 
moment - so I’m torn.

One thought to try to make the repeated code concise is to use an 
“EventMetadata” record that would become the parent of the id, timestamp, and 
user fields - and have the “EventMetadata” record be part of the PolarisEvent 
interface. We can then add a quick helper method wherever required to generate 
a new “EventMetadata” record - and we can limit the bloating of the Event 
record variables. Any further PolarisEvent-wide variables should be then added 
to the “EventMetadata” record as well. I know this option is not ideal either - 
but presents the functionality at the least impact to the code that I can see. 
WDYT? (Sample code in Appendix, if it’s helpful :)

Best,
Adnan Hemani

——
Appendix:

public record EventMetadata(UUID uuid, Instant timestamp, PolarisPrincipal 
user);

public interface PolarisEvent {
  EventMetadata eventMetadata();
  default static UUID createEventId() { return UUID.randomUUID(); }
}

public record BeforeSomethingEvent(String someParam, EventMetadata em) 
implements PolarisEvent {}

Instantiation:

@Inject SecurityContext securityContext;
@Inject Clock clock;

private EventMetadata createEventMetadata() {
  return new EventMetadata(PolarisEvent.createEventId(), clock.instant(), 
(PolarisPrincipal) securityContext.getUserPrincipal());
}

new BeforeSomethingEvent(“abcd”, createEventMetadata());

> On Oct 24, 2025, at 2:40 AM, Alexandre Dutra <[email protected]> wrote:
> 
> Hi Adnan,
> 
> I agree that Java records lack some flexibility but I think we can
> achieve a nice layout. Here is a tentative design:
> 
> public interface PolarisEvent {
>  UUID id();
>  Instant timestamp();
>  PolarisPrincipal user();
>  static UUID createEventId() { return UUID.randomUUID(); }
> }
> 
> Here is a typical event record:
> 
> public record BeforeSomethingEvent(
>    UUID id,
>    Instant timestamp,
>    PolarisPrincipal user,
>    String someOtherParameter)
>    implements PolarisEvent {}
> 
> And here is a typical event instantiation idiom:
> 
> @Inject SecurityContext securityContext;
> @Inject Clock clock;
> 
> new BeforeSomethingEvent(
>    PolarisEvent.createEventId(),
>    clock.instant(),
>    (PolarisPrincipal) securityContext.getUserPrincipal(),
>    "someParameter");
> 
> What do you think of the above?
> 
> Thanks,
> Alex
> 
> On Fri, Oct 24, 2025 at 10:15 AM Adnan Hemani
> <[email protected]> wrote:
>> 
>> Hi Alex,
>> 
>> I had thought about this earlier and abandoned the idea due to 
>> implementation concerns. So, my question is how exactly will these methods 
>> be implemented and used?
>> 
>> Let’s take id() as the talking point, but similar points can be applied to 
>> timestamp(). PolarisEvent is an interface and all implementations of this 
>> interface are Java records - as a result, neither PolarisEvent nor its 
>> implementations can contain non-static instance fields. So if a PolarisEvent 
>> cannot save its ID through a non-static instance field, how do we guarantee 
>> idempotency if id() is called twice on an instance of PolarisEvent?
>> 
>> The other way I see is we instead include id, timestamp, and user as named 
>> arguments of every record signature along with a custom constructor which 
>> will generate the new ID and timestamp - which makes the code extremely 
>> messy IMO (and was one of the original reasons why we stuck to using an 
>> Interface for PolarisEvent rather than switching over to an ABC, which I had 
>> proposed earlier). Or we go towards using an intermediate ABC with 
>> PolarisEvent re-written as a Sealed Interface, with each record extending 
>> the intermediate ABC. I’m not sure I understand the ramifications of doing 
>> this yet, so cannot endorse this approach either.
>> 
>> Did I miss some other way to make this work? Or were you thinking of 
>> something completely different?
>> 
>> Best,
>> Adnan Hemani
>> 
>>> On Oct 23, 2025, at 10:18 AM, Alexandre Dutra <[email protected]> wrote:
>>> 
>>> Hi all,
>>> 
>>> Just to be clear, and sorry for the confusion: the idea was to add
>>> those methods to the PolarisEvent interface, not PolarisEventListener.
>>> 
>>> I agree with Mike that PolrisEventListener shouldn't deal with context
>>> data. In my mind, each PolarisEvent instance would carry its own ID,
>>> timestamp (at creation time) and user (the principal that triggered
>>> the event).
>>> 
>>> Thanks,
>>> Alex
>>> 
>>> On Thu, Oct 23, 2025 at 7:08 PM Michael Collado <[email protected]> 
>>> wrote:
>>>> 
>>>> +1 on adding the described fields to the PolarisEvent.
>>>> 
>>>> I don't think the PolarisEventListener is really the right place to add any
>>>> default methods about context, though. I'm not sure if you meant adding
>>>> those to the PolarisEvent interface so that existing records have a default
>>>> value?
>>>> 
>>>> Mike
>>>> 
>>>> On Wed, Oct 22, 2025 at 10:26 PM Jean-Baptiste Onofré <[email protected]>
>>>> wrote:
>>>> 
>>>>> Hi Alex
>>>>> 
>>>>> It makes sense to me.
>>>>> 
>>>>> Are you also proposing to have default methods in the interface ?
>>>>> 
>>>>> interface PolarisEventListener {
>>>>> 
>>>>> default UUID id() { ... }
>>>>> 
>>>>> default Instant timestamp() { ... }
>>>>> 
>>>>> default PolarisPrincipal user() { ... }
>>>>> 
>>>>> }
>>>>> 
>>>>> Maybe we can have a default behavior that implementations can
>>>>> "override" if needed.
>>>>> 
>>>>> Regards
>>>>> JB
>>>>> 
>>>>> On Wed, Oct 22, 2025 at 5:08 PM Alexandre Dutra <[email protected]> wrote:
>>>>>> 
>>>>>> Hi all,
>>>>>> 
>>>>>> We have two (albeit incomplete) PolarisEventListener implementations
>>>>>> today: one for JDBC and one for AWS CloudWatch.
>>>>>> 
>>>>>> Both enhance event payloads internally by adding a timestamp and user
>>>>>> data. These two attributes are indeed very common in event-oriented
>>>>>> systems.
>>>>>> 
>>>>>> The JDBC implementation also generates unique IDs internally. That's
>>>>>> because the PolarisEvent interface lacks an id() method, despite the
>>>>>> common need for event disambiguation.
>>>>>> 
>>>>>> Therefore, I propose promoting these three attributes (ID, timestamp,
>>>>>> user) to the PolarisEvent interface, by adding the following methods:
>>>>>> 
>>>>>> UUID id();
>>>>>> Instant timestamp();
>>>>>> PolarisPrincipal user();
>>>>>> 
>>>>>> This information is readily available at event instantiation sites,
>>>>>> and can be provided via CDI injection without hassle.
>>>>>> 
>>>>>> Let me know your thoughts.
>>>>>> 
>>>>>> Thanks,
>>>>>> Alex
>>>>> 
>> 

Reply via email to