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
>>>>>
>>