Multi-Service Analytics. In today’s context, nearly every… | by Svitlana Kravchenko | Sep, 2023
Svitlana Kravchenko

In today’s context, nearly every application relies on analytics. Multiple services are frequently utilized, and there are occasions when one service is swapped for another, resulting in substantial efforts for their integration or replacement.

When searching for a suitable service for gathering analytical data in one of our projects, we concurrently used multiple services, adjusted the data sent for analysis, and added or removed services. This prompted the idea of streamlining our workflow and adopting a more versatile approach that allows us to seamlessly add or remove analytics platforms in real-time.

Now, I’ll outline the concept and describe its implementation.

The concept is as follows: create an interface (Subscriber) that defines application events. It includes a method (update) that, based on the event, sends the necessary data to the platform and abstract methods for each event type. Next, an implementation is created for each platform, specifying the data and format to be sent. The advantage of this approach is that it accommodates different data formats for each platform with ease. Conversely, a Publisher is established, housing instances of Subscribers and notifying them when events occur. This dynamic list of subscribers enables you to add or remove them during runtime.

Let’s dive into the code.

First, define a list of events in the application. I used a sealed class for this purpose because it allowed me to pass specific event data when sending the event to the Subscriber.

sealed class AnalyticsEvent 
class LogIn(val succeeded: Boolean, val error: String?, val userId: String = "") : AnalyticsEvent()
object LogOut: AnalyticsEvent()
class OrderAccepted(val order: Order, val error: String?): AnalyticsEvent()
class OrderRejected(val order: Order, error: String?): AnalyticsEvent()

Next, let’s describe the Subscriber:

interface Subscriber 

fun trackLogIn(succeeded: Boolean, error: String?, userId: String = "")
fun trackLogOut()
fun trackOrderAccepted(order: Order, error: String?)
fun trackOrderRejected(order: Order, error: String?)

fun update(event: AnalyticsEvent)
when (event)
is AnalyticsEvent.LogIn -> trackLogIn(event.succeeded, event.error, event.userId)
is AnalyticsEvent.LogOut -> trackLogOut()
is AnalyticsEvent.OrderAccepted -> trackOrderAccepted(event.order, event.error)
is AnalyticsEvent.OrderRejected -> trackOrderRejected(event.order, event.error)


The implementation of a specific subscriber may appear as follows:

class FirebaseSubscriber(
private val firebaseAnalytics: FirebaseAnalytics
) : AnalyticsTracker
override fun trackLogIn(succeeded: Boolean, error: String?, userId: String)
// Put data about the event

override fun trackLogOut()
// Put data about the event

override fun trackOrderAccepted(order: Order, error: String?)
// Put data about the event

override fun trackOrderRejected(order: Order, error: String?)
// Put data about the event

Now, create the Publisher:

class Publisher {

private val subscribers: MutableList<AnalyticsTracker> = mutableListOf()

fun subscribe(analyticsTracker: AnalyticsTracker)
subscribers.add(analyticsTracker)

fun unsubscribe(analyticsTracker: AnalyticsTracker)
subscribers.remove(analyticsTracker)

fun notifySubscribers(event: AnalyticsEvent)
subscribers.forEach
it.update(event)

Finally, all that remains is to create instances of Publishers and Subscribers, subscribe the subscribers using the subscribe() method, and notify the subscribers when an event occurs:

analyticsPublisher.notifySubscribers(AnalyticsEvent.LogIn(…args))

In the end, if you need to add another analytics platform, you only need to create a Subscriber implementation and subscribe it to the update method. To remove one, simply unsubscribe it. Furthermore, you can subscribe or unsubscribe during runtime, greatly simplifying the process and avoiding unnecessary complexity in certain cases.

By