snowplow / snowplow-java-tracker

Snowplow event tracker for Java. Add analytics to your Java desktop and server apps, servlets and games. (See also: snowplow-android-tracker)
http://snowplowanalytics.com
Apache License 2.0
23 stars 36 forks source link

Attempting to use both the Java and Android tracker in the same project causes DuplicateClass errors #383

Open mezpahlan opened 1 month ago

mezpahlan commented 1 month ago

Describe the bug I have a library A that uses the java-tracker as a dependency. It's a pure Java project because it needs to be shared with other pure Java projects. That is to say it uses the Gradle java plugin.

I also have an application B that uses the android-tracker as a dependency. This uses the Gradle android-application plugin but otherwise contains a mix of Android libraries and Java libraries.

Both scenarios are valid until you try and use library A in application B as dependency. Gradle then complains that there is a DuplicateClass error.

Duplicate class com.snowplowanalytics. snowplow. tracker. DevicePlatform 
found in modules 
snowplow-android-tracker- 6. 0. 3. aar -> snowplow-android-tracker- 6. 0. 3- runtime (com.snowplowanalytics: snowplow-android- tracker: 6. 0. 3) 
and
snowplow-java-tracker- 2. 1. 0. jar -> snowplow-java-tracker- 2. 1. 0 (com.snowplowanalytics: snowplow- java- tracker: 2. 1. 0) 

To Reproduce

  1. Publish a pure Java library that depends on the Snowplow java-tracker to your Maven Local repo.
  2. Configure an Android application to additionally check your Maven Local repo.
    repositories {
    mavenLocal()
    ... // Other repos you might need
    }
  3. Add to your Android application a dependency on the library that you published in Step 1.
  4. Add to your Android application a dependency on the Snowplow android-tracker.
  5. Build application.

Expected behaviour

The Android application can be successfully compiled with both the java-tracker and the android-tracker side by side.

Additional context

I originally wanted to use the android-tracker as a dependency to the library I am building (library A) but I cannot import an Android .aar into a project that only depends on Java. The usage of Snowplow in Library A is to send events about Library A independently of whatever application Library A may be embedded in - be that Java or Android.

It looks to me that it is only DevicePlatform (in the Java tracker, in the Android tracker) that causes the issue due to the matching namespace and class name.

I really like the android-tracker compared to the java-tracker as I mainly write Kotlin, but there isn't a way for me to build a pure Java SDK that can depend on the android-tracker at the moment and I realise refactoring the android-tracker to separate Android dependencies into another artefact (perhaps two Kotlin written SDKs called snowplow-core and snowplow-android) is a bit much to ask.

As a quick win would you consider renaming DevicePlatform in the java-tracker? Or moving it to a package that won't cause a collision?

matus-tomlein commented 1 month ago

Hi @mezpahlan, thank you for raising this! We haven't seen this type of use case before.

I would advise against using both the Java and Android trackers in one project. Mainly because then the events will have different properties – each tracker will have different user and session identifiers (the Java tracker may even be missing those) and context entities. It will be difficult to make sense of these events in the warehouse and join them.

One option to consider would be for the library A to accept a tracking interface that would be implemented in the application B. So for example, you could define an interface in library A that could look like:

interface TrackingInterface {
   void trackScreenView();
    ...
}

Then the application would implement this using the Android tracker and pass an instance to the library to use to track events.

In this setup, the library doesn't need any dependency on the Java or Android trackers.

mezpahlan commented 1 month ago

Thanks @matus-tomlein 🙏

I would advise against using both the Java and Android trackers in one project. Mainly because then the events will have different properties – each tracker will have different user and session identifiers (the Java tracker may even be missing those) and context entities. It will be difficult to make sense of these events in the warehouse and join them.

It might sound odd but this is what we're trying to achieve. Apologies I should have elaborated a bit more in the original post.

We want the java-tracker embedded into Library A to send events that are completely independent of anything that may or may not exist in Application B. To be more precise, we want Library A to send Telemetry style Events about Library A only. So having different user or session identification is acceptable for us in that narrow use case. We wouldn't be looking to join these to anything that Application B might be sending.

An example of this is:

  1. We distribute Library A to multiple teams internally and expect them to update to supported versions in a timely manner (for support and critical bug fix reasons). We want to know what the actual versions of Library A being used in the wild without having to ask multiple teams.
  2. We want to collect timing data for certain functions within the SDK in production so that we can validate our performance assumptions.
  3. We want to understand the usage of Library A's public API with a view to improving it based on actual usage.

None of these Events need to be joined back to the user that might be using Application B.

One option to consider would be for the library A to accept a tracking interface that would be implemented in the application B. So for example, you could define an interface in library A that could look like:

Another question we had was "What if Application B didn't have any Snowplow dependencies? In that scenario we still want our Library A Telemetry Events to be sent via the Snowplow java-tracker that is a dependency of Library A.

I appreciate this is a very niche use case 😅, but what we're looking for is:

This works if Application B is another Java application (for example a Spring Boot backend application), or if Application B is an Android application that doesn't yet depend on the android-tracker.

matus-tomlein commented 1 month ago

Ah, I see, thanks for the context @mezpahlan!

In that case, I think you're correct in wanting to use both the Java and Android tracker and I don't see other solution than changing the namespace of the DevicePlatform (or the whole Java tracker). Unfortunately that's a breaking change so will need to go in the next major version. That might take a bit longer for us to put together as we prefer to limit the number of major versions.

For the short term, if you are able to fork the repo, change the namespace and build a jar that you can use in your library, that might be a better option.

mezpahlan commented 1 month ago

Totally understandable. Yup, thanks I'll see about forking the repo for the time being.