Hi Adnan,

Sure, EventMetadata works for me.

I think we are moving into implementation details a bit prematurely
though. We don't need the exact final design right now, as long as we
can demonstrate – as we did – that what's proposed can be easily
achieved.

Thanks,
Alex

On Sat, Oct 25, 2025 at 11:44 AM Adnan Hemani
<[email protected]> wrote:
>
> 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