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