Although we have written before about the process of building the DNSimple Slack app, there is one topic that we didn't cover in that post: what we did to learn about how our customers use the app.

Here is what we wanted to track:

  • How many customers signed up.
  • How many of the sign-ups required a subscription upgrade.
  • How many domains were registered through the app.
  • Which commands were the most used.

We also knew that we didn't want to put a lot of time (or money) into developing the data tracking since we had already invested quite a bit in the project. Finally, we didn't want to tie ourselves to any specific tool, since there was so much uncertainty surrounding the project.

Baking flexibility in

After thinking a little bit about it, we decided that the easiest thing to do was to use events: We could listen to them and react in any way we wanted. Should the usage tracking become irrelevant at any point in time, we could simply remove the corresponding listeners and leave the application untouched.

The next decision to take was how to implement the event system. Our first impulse was to look at gems that we had used before. However we prefer to avoid pulling in dependencies to a project unless they are really justified.

We then looked into what is availabile in the Ruby Standard Library. We realized that we could build a very simple event bus using Ruby's Observable library. Here is what we came up with:


module DnsimpleSlack
  module Events

    def self.publish(event_name, event_payload = {})
      event_bus.publish(event_name, event_payload)
    end

    def self.subscribe(event_name, &callback)
      event_bus.subscribe(event_name, &callback)
    end

    def self.event_bus
      @event_bus ||= SimpleBus.new
    end

    class SimpleBus
      include Observable

      def publish(event_name, event_payload)
        changed(true)
        self.notify_observers(event_name, event_payload)
        changed(false)
      end

      def subscribe(event_name, &callback)
        subscriber = Subscriber.new(event_name, callback)
        self.add_observer(subscriber)
      end

      class Subscriber
        def initialize(event_name, callback)
          @event_name = event_name.to_sym
          @callback   = callback
        end

        def update(event_name, event_payload)
          @callback.call(event_payload) if subscribed_to?(event_name)
        end

        private

        def subscribed_to?(event_name)
          @event_name == event_name.to_sym
        end
      end

    end
  end
end

With this code in place we could report events from anywhere in the app like this:

Events.publish(:command_received, {
  type: command.name,
  text: command.text,
  date: Date.today.strftime("%Y-%m-%d"),
  user_name: command.user_name,
  team_slack_name: command.team.slack_team_name,
  team_dnsimple_account: command.team.dnsimple_account_email,
})

The simplest tool that could possibly work

With the event system in place we only needed to add subscribers to the events.

Next we had to decide how we were going to visualize this information. Since we didn't know what the volume of data was going to be, and given it was going to be very easy for us to replace the code that managed the events, we did the simplest thing we could come up with: logging the data.

Here is how one of our event listeners looks like:

DnsimpleSlack::Events.subscribe(:command_received) do |payload|
  DnsimpleSlack.logger.info("@EVENT> COMMAND_RECEIVED: #{JSON.dump(payload)}")
end

Then we made sure to have some built in searches in our log management tool to make this information easily available.

One year after the release of the app, we are still using this solution today. So far we can get all the information we need from it. We couldn't be happier we kept it as simple as this.