How to implement analytics and not break the application?

Hello! My name is Sosnin Ilya. I work at Lamoda Android developer. I paint buttons, skip lists and, unfortunately, I write analytics ...



Lamoda is a Data Driven Company in which all decisions are made based on user behavior. First we observe and only then draw conclusions. Therefore, it is easy to guess that we have analytics, and we really need it.



In deciphering my report of the mitap Mosdroid # 18 Argon, I will tell you how our SDK works and why reflection is not always bad. And also I will answer the main question of this topic: "How to implement analytics and not break the application?".

image

To begin with, I’ll ask one simple question: “How do you think, how many installations do we have on Google play?”



10 million installations!

image

Indicator at the beginning of July 2019.



Besides the fact that we draw conclusions based on users, we also have internal customers who are also interested in analytics.

image



First of all, analytics is needed for marketing for their own research purposes. R&D controls our search queries at the expense of it, and products run on new features.



For example, we had a feature that allowed us to collect the whole image as a whole. That is, you could buy not only the shirt you liked on the model, but also pants from the same image. We decided that for a start we will write it for IOS, and then we will think about whether we need it at all. They wrote, looked and made sure that users did not like it.



image



What do you think should be done with features that people don’t need?



Right, throw them away! This is especially worth doing when the feature is tied to external services, because they tend to receive problems or may be paid. This happened with this feature. We did not implement it on either Android or Desktop, but instead decided to evolve it. (maybe someday she will go to the prod in a more perfect form).



What is the difficulty?



No matter how funny it may sound, when introducing analytics, it can be difficult to work with the analysts themselves . Most often, conflicts arise because they ask for data that you don’t have. And it always ends with the fact that you still have to drag a bunch of parameters through 10 screens to send them one small event. And this happens quite often.



The second challenge is collecting analytics . We have this process carried out in 7 systems.



image



Some events go to one system, others go to several at once ... Moreover, there is such a feature that events with different parameters and in different formats can go to different systems. Of course, we don’t really want to resolve all these dependencies.



LStat is our own SDK (Lamoda statistics). This is a massive system, which takes more than 60% of various events. Those events that go further to Google, Adjust, were often initially collected only in LStat.



SDK



Our SDK is as follows.

image



A clean LStat sticks out, which inside consists of two parts: storage and shipping. When we collect an event, we don’t send it immediately. Otherwise, there would be too many events and requests, which is not very convenient. Therefore, we put everything in our small SQLite database, where we store everything. Then, at some intervals, our Network Layer pulls the data from the database and sends it.



After we received confirmation from the server that the events have arrived, we clear our database. This process occurs regularly. Thanks to this, our base does not grow, and we guarantee the delivery of all events. If for some reason the event has not arrived, then it will be stored in our database until a response is received from the server.



Collectors



As I said earlier, we have 7 collectors. They consist of such methods: custom annotation, EventHandler and AppStartEvent. What do you think this event tracks?

image



Of course, this is a cold start to the application. And the main thing here is that we have an AppStartEvent class that inherits from some Event interface. And why do we need this, I will tell a little later.



How is it going? This is where the thrash, burning and reflection begins.

image



First we go through all of our 7 collectors. Then we pull out the Java class and collectorName, which we need later for storage.



Further, from this Java class, we take out all our methods that are in this code. Now we need to check and make sure that our method is a track-event method that will be responsible for storage. To do this, we have several parameters: the first is that we have the @EventHandler annotation, we do not have an empty list of parameters, and some event comes to the input.



All conditions are met, so we can assume that this function will be an event with us. I just wrap it in some wrapper and send it to our collection.



Reflection is not always bad



Yes, many of you will say that reflection is bad, slow, terrible.

image

To begin with, it can be either slow or fast. There are methods like getFields, getConstructors that work very fast relative to the rest of the reflection. And there is, for example, Constructor newInstance, which works really slowly. By the word “slow” I mean the difference between the left and right columns in the table above by several orders of magnitude (approximately a hundredfold difference). Therefore, if you understand what you are doing and know in advance what you need to be prepared for, then not everything is so scary.



We are pulling more than 500 methods from 7 classes . And we do it only once per session. The time taken to complete a passage is 40 milliseconds. This is less than 3 frames (at the splash screen stage). And it was far from a top-end device, but a simple NTC on Android 6, which has been around for many years.



Of course, that on a top-end device everything will work faster. And if we talk about old Chinese phones, then the time spent there will be a conditional 100 milliseconds. Users of such phones are already used to the fact that everything works slowly for them, so they are deeply indifferent to 40 milliseconds or 100 there. What's the difference? They still slow down :)



And now the main question: how to implement analytics so as not to break the architecture?



Architect



Our application uses MVP.

image



This is our kind of god-entity, which “lives” on ApplicationScope and is injected exactly where we need it. For example, we need to deposit onClick (). In order not to break the architecture, we will not forward the event from the View layer to Presenter, so that later it will go somewhere. Instead, we directly do everything from the View and pass the track to the AnalyticsManager.



And now a little about sending. At AnalyticsManager one method sticks out - this is the track method, which accepts any event class as an input. And then black magic happens.



image



This method is able to resolve all our problems.



Firstly, it will help to deposit in several different systems . Handlers are all our events that will ever be collected. Next, we look for the desired method here. Accordingly, if we have a track event written, for example, in 4 collectors, then it will be stored in 4 copies. That is, in 4 passes of the cycle we will find it and send it to all 4 systems with the corresponding parameters.



Secondly, it helps to solve the problem with one-time events . These are events that must be logged strictly 1 time for the entire cycle of the application. Mark e.once, the usual boolean variable. If we say that this is a one-time event, then we simply delete it from the collection. What happens next if we try to pawn it again? Obviously, we simply will not find it in this collection. You can try to shoot yourself in the foot as much as you like while continuing to write analyticsManager.track (AppStartEvent ()), it will still go once and there will be no more.



What is the profit?



1. We do not break the architecture of our application , since the AnalyticsManager lies and works outside the architecture. This allows us to embed it in any part of the application.



2. Allows you to collect any events in one line in any number of analytics systems. To do this, we simply write: analyticsManager.track (Event ()). Because then he himself resolves where, in what quantity, with what parameters, when and so on.



3. Solves the problem of “one-time events” . Now we do not need to do various kinds of checks. Once sent, removed, and we will not meet him again.



4. Solves the problem of collecting the same element in different systems . Due to the fact that we wrote everything in different collectors, it went to different collectors. Thus, you do not have to resort to unnecessary gestures. In my opinion, this is wonderful.



Testing...



We test manually. Why not automate, you ask? And here the sad thing turns out.



Firstly, unfortunately, you will not normally cover this with a unit test. Because most of the problems with events in analytics do not arise due to the fact that some of the parameters you did not gather. In my personal experience, 90% of the problems arise because you did not send the event where it was supposed to go. Such cases can only be caught with UI tests, which we have not yet written for all this.



And secondly, for the time being we rest a little on the fact that analysts describe events in a fairly strict format (in confluence), but this format is not a strict specification format, such as Swagger. Accordingly, there are small discrepancies, there are duplicates (although in this case more often just a link to another page is made). For the time being, this limits us in the possibilities of automation of analytics testing. But we are working on it.



findings



What further can be done with this decision?



1. Write autotests for analytics . This will require a lot of preparatory work. But it cannot be said that this is impossible.



2. Methods tracks plus or minus are quite similar. In each, we generate some “universal” parameters that everyone needs. And just collect the map of values. In general, one could write either a plug-in or a utility . Never mind. The main thing is that she generate a track event for the respective classes.



But here a small problem may arise if the events start to go away a little differently (for example, for different analytics systems). Because of this, not everything can be solved adequately. And besides, I don’t want to produce unnecessary code generation in a project, which there are so many in many projects on android (hello Dagger, Moksi and other ones working on codegen).



3. Integration of the application with specs of analysts . This is probably too far-fetched dream, but still ... We would really like our analysts to write in a strict format, and we could parse their works of art and integrate. Then peace and harmony would come. Everyone would be happy :)



So what do I want to say with all this?



First, analytics is still needed. Because it allows you to save money, resources and effort. This saves you from writing unnecessary code or deleting code that seems to be not old, but is still not needed here. Less legacy is always good.



Secondly, reflection is not always bad. Yes, it is slow. But sometimes we lose quite a bit in performance, but we get a lot, for example, in the field of development and error handling.



And thirdly, we are laying the analytics at the feature planning stage. Due to this, we can negotiate with analysts much earlier, reaching a compromise. And also it gives us the opportunity to estimate the time spent writing analytics in advance.



All Articles