February 28, 2023

Writing an Analytics Server using Vapor - Intro

I have a project that's very near to being ready to release on the app store, but I realized that I am going to want some data about how users are using the app so that I can make decisions about how to improve it.

I could use a third party framework like Google Analytics for this, but I prefer not to be dependent on third-party frameworks whenever possible. Also, it's been a while since I did any server-side development, and I thought that this would be a great opportunity to re-introduce myself to Vapor.

My background in Vapor

I've only ever played around with Vapor, and not for a few years. I read Paul Hudson's excellent book on Vapor and did most of the worked examples in that book a few years back. I've watched a few of Mikaela Caron's videos on Vapor development and plan to continue watching the rest. I've also read much of Vapor's documentation, at least enough that I think I have a general idea of how Vapor works and how to find things if I need help.

What I Want

I want a simple analytics engine that:

  • has an endpoint where my app can report when users do certain things
  • stores these events in a database
  • lets me access the data via another endpoint(s) to see what users are doing.

Basic Structure

My actual use case is a little more specific than this, but for the purposes of this blog series, I'll build a server that stores something like the following:

struct UserEvent {
    let id: UUID
    let userID: UUID // just used to see which events are associated with a given user
    let date: Date // the date that the user did the action
    let userAction: UserAction // a description of what the user did
    let flag: Bool // a Boolean flag that's passed in the request
}

the UserChoice should be a string-backed enum that's passed as a string in the request

enum UserAction: String {
    case start, pause, stop
} 

and I do want to associate these with a separate User type in a relational way, though I don't want to store anything specific about the user

struct User {
    let id: UUID
}

Logging User Actions

I'd like my app to send a request that looks something like:

POST /userevent HTTP/1.1
content-type: application/json
content-length: ?

{
"userID":"some_user_id",
"date":date_as_unix_epoch,
"action":"start",
"flag":false
}

I'd like it to receive back a 200 on success and an appropriate error on failure.

Retrieving User Actions

I'd like to be able to retrieve the data with something like:

GET /userevent?action=_some_user_action_&start=_date_as_unix_epoch&end=_later_date_as_unix_epoch HTTP/1.1
content-type: application/json

and get back something like:

[{
    "id","some_id",
    "date", date_as_unix_epoch,
    "userAction", "start" 
    "flag", false
}
{
    "id","some_other_id",
    "date", other_date_as_unix_epoch,
    "userAction", "start" 
    "flag", true
}
...
]

and I can imagine other similar endpoints to get data in different ways.

Access Restrictions

Eventually, I do want some kind of access restriction so that only requests coming from my app are added to the database and only someone with my credentials can view the data. These will probably be steps I take later, after I've gotten the basic logic of the server working.

Strategy

I want to work from the ground up, using test-driven-development for as long as possible. I want to have as much of the server's logic working as possible before I start testing it locally, and I'd like to have things working exactly as I want before trying to deploy for the first time.

I want to avoid using outside tools for as long as possible.

I want to use async/await as much as possible. At this moment, I think I can use it for all my database code. If I can avoid using Promises and Futures, I'll be happy.

For storage, I'd like to use a SQL database, probably PostgreSQL for deployment, but I'll probably use just an in-memory MySQL database for development. I believe that it's pretty straightforward to use one kind of database for development and another for deployment. I will find out.

I will use one or more controllers to modularize the project, following the example in the new Vapor project.

I will try to do this just by following the Vapor documentation and perhaps doing the occasional google search. The other resources I've used in the past are not up to date with Vapor's use of async/await. I know that there's a Vapor Discord, and I'll keep it in mind, but I'd like to see what I can figure out on my own.

I will try to document my experiences in this blog series, and post my results to github. Hopefully, this blog series can be useful to other people trying to learn Vapor, or at least it can be entertaining to people who already know it.

Posts in this Series:

Tagged with: