amplitude / Amplitude-Kotlin

Amplitude Kotlin SDK
MIT License
28 stars 11 forks source link

Generic Event abstraction #98

Closed matejuh closed 2 months ago

matejuh commented 1 year ago

Summary

Event is generated by Ampli per source which causes complications when trying to use events in some generic way.

Motivations

We use Amplitude to define our events. Ampli generates typesafe classes for them. But we proxy them through Segment and Amplitude is only one of destinations. Old way used Amplitude client with Segment plugin. Now we use directly Segment client and pass there event type and event properties because Segment Event is kind of compatible. But we are not able to do that in better way than to pass properties as map because Event is generated per source and we share analytics as library into multiple sources. We would like to have Event implementing a global interface providing type and properties. This interface we can pass into our library.

liuyang1520 commented 1 year ago

Hi matejuh,

Thank you for supporting Amplitude!

Event is generated by ampli per source, is because different source might be different language, say Kotlin, TypeScript. I am not quite sure what is the global interface you proposed here, would you minding providing an example for us to better understand the problem?

Thanks!

matejuh commented 1 year ago

Hello, thanks for quick response. Here is my problem. Imagine sources as two modules or apps using shared library.

Source1:

package com.matejuh.source1

import com.matejuh.AmplitudeEvent

abstract class Event<E: Event<E>>(
    override val eventType: String,
    override val eventProperties: Map<String, Any?>?,
    val options: EventOptions?,
    private val eventFactory: (eventProperties: Map<String, Any?>?, options: EventOptions?) -> E
): AmplitudeEvent

Source2:

package com.matejuh.source2

import com.matejuh.AmplitudeEvent

abstract class Event<E: Event<E>>(
    override val eventType: String,
    override val eventProperties: Map<String, Any?>?,
    val options: EventOptions?,
    private val eventFactory: (eventProperties: Map<String, Any?>?, options: EventOptions?) -> E
): AmplitudeEvent

What I added is that AmplitudeEvent interface which looks like:

interface AmplitudeEvent {
    val eventType: String
    val eventProperties: Map<String, Any?>?
}

That helps me to work with both events with generic way as:

class SegmentService {
    fun sendToSegment(event: AmplitudeEvent) {
        val jsonProperties = event.eventProperties?.toJsonElement() as JsonObject?
        val segmentEvent = TrackEvent(event = event.eventType, properties = jsonProperties ?: emptyJsonObject).apply {
            this.userId = userId ?: userId()
        }
        segmentClient.process(segmentEvent)
    }
}

Is it more clear now?

liuyang1520 commented 1 year ago

Hi matejuh,

Thank you for describing the problem with example! I have also shared it to one of my teammate in case I miss anything.

For the AmplitudeEvent, we have a BaseEvent, it is not an Interface, but we also use it for the base event class. Is it possible for you to use it?

Another thought is even if you need the AmplitudeEvent in both Source1 and Source2, why not creating a common module like sharedModule then put all shared interfaces in it. In this case you can customize whatever event interface you need. While this interface is not needed in this Amplitude-Kotlin SDK at the moment.

Let me know if this doesn't answer you question. Thank you and happy holidays!