List functional requirements for the system (Ask the chat bot for hints if stuck.)...
List non-functional requirements for the system...
Define what APIs are expected from the system...
startActivity(activityType ActivityType) -> activitySessionId
Records starting of an activity. ActivityType should be an enum indicating the actual activity (e.g. running, jogging, swimming).
API returns a activitySessionId to record the activity.
endActivity(activitySessionId)
Records ending of the activity corresponding to the session id.
setGoal(targetDuration Duration)
Set the daily goal for activity duration.
syncProgress(currentDuration Duration)
Sync the current progress of the user.
POST v1/users/{userId}/activities (Recording an activity)
startTime: timestamp
endTime: timestamp
activityType: enum (Running, Jogging, Swimming, etc)
energyConsumed: Float
mileage: (for certain activities, record the mileage)
Response: 200 for succeeded, 400 for invalid request (endTime < startTime, invalid activityType, etc)
activityId: idempotency token
Get v1/users/{userId}/activities
Response:
Activities (list): startTime, endTime, activityType, mileage
Get v1/users/{userId}/activities/{activityId}
Response:
Activity: startTime, endTime, activityType, mileage
GET /v1/users/{userId}/stats/summary
Query Parameters: startTime, endTime, metrics (steps, calories, all...), interval (daily, weekly, monthly)
Response:
period: {startTime, endTime}
metrics: {steps: ..., calories:...}
trends:
[
{startTime1, endTime1, calories1}, // Actual metrics depends on users query
{startTime2, endTime2, calories2}
...
]
POST /v1/integrations/webhooks/{provider} //Webhook for external system to push the workout information
Response
externalActivityId:String // Using this to pull the data from their system.
userId: String // So that we know which user to update
POST /v1/integrations/{provider}/sync?lookbackDays
Query Params:
lookbackDays: Days to lookback
userId: String
You should identify enough components that are needed to solve the actual problem from end to end. Also remember to draw a block diagram using the diagramming tool to augment your design. If you are unfamiliar with the tool, you can simply describe your design to the chat bot and ask it to generate a starter diagram for you to modify...
The service itself should not be complicated and requires the normal components - client, server and database.
For analytics purposes, there are two additional components. A cron job (or jobs) reading from the database periodically and write to a seperate analytics datastorage.
API Gateway - This component should handle rate limiting, which should be able to protect the application during spikes of traffic.
UploadQueue + asyncUploadHandler - when a large number of activities get upload or synced from devices / external systems, the API handlers should put the updates into an upload queue, so that they're handled asynchrounously
Server and ThirdPartyActivitySystem: The user either record their own activities using a client, or sync from third parties. The client should generate idempotency key for each activity to avoid duplication, and we also expect to use external activity id from third party for dedup. When updating, the server will check if the database has already got such an activity stored.
analyticsUpdateQueue and analyticsUpdateHandler: If a server has handled update of user's activity, it should put a message into analyticsUpdateQueue so that its handler can later on handles how this activity has affected user's daily / weekly / monthly analytics, and how it contributes towards user's goals. The asyncUploadHanlder should also fire message into analyticsUpdateQueue as it process user actvities
Cache: A cache can be used to hold recently updated data, as user is likely to read them. The cache should not be used as a source of truth, so write functions should always write to database first and then write to the cache. If database read is taking too long, the server can returns data in the cache even if it is stale, and the app should shows that the client is displaying offline data.
Client: Client should be able to handle network interruption. If network is not available for some reason, the app should be able to store the newly added history locally, and attempt again when network is back. If users have multiple attempt of updating activities during network interruption, these multiple upload can be handled with certain delays so as not to overwhelm the server.
Dig deeper into 2-3 components and explain in detail how they work. For example, how well does each component scale? Any relevant algorithm or data structure you like to use for a component? Also you could draw a diagram using the diagramming tool to enhance your design...
Client: The client contains not only the UI but also a portion of user data, specifically, the user progress data. To prevent lost of data, the client should sync with server in the following situation: 1. every hour at a certain offset. 2. when user completes an activity, it should call endActivity as well as syncProgress. 3. when a user close the app.
Analytics datastore: The app itself seldom needs to access historical data, therefore, in order to keep database efficient, it should only contains latest 1 year of data. The historical data, however, could be valuable for analytics purposes, therefore, a cron job should fetch them periodically and store all data in this analytics datastore and store them in format that would be more efficient and convenient for analytics purposes. Besides, in the event of network interruption or other situation where server is unreachable, the client should have a local queue to store the event locally and upload when the connection is restored.
Idempotency keys: As mentioned above, when the client records an activity, it should generate an idempotency key for each of them, so that if there are re-uploads due to network issues, etc. the database should not process the same activity twice. Also, when user syncs the activities from external party, our system should require that those integrated system also have an id (which is likely available). That unique id should be used to identifies if the same activity has been uploaded.
Addition constraint to identify duplicate activity: Idempotency key cannot prevent the issue where user uploaded an activity, and then sync the activity from an external source. Therefore, upon updating databases for new activities, the database check should include a contraint that no same activity exists. Basically, uploading an activity should only be successful if there are no overlap of activity in the same time period. The example in ProsgreSQL:
ALTER TABLE activities
ADD CONSTRAINT no_overlapping_activities
EXCLUDE USING gist (user_id WITH =, activity_range WITH &&);
This will ensure the table include the constraint.
AsyncUploadQueue and handler: As mentioned above, large uploads and syncs should be handled asynchronously in a queue, therefore the API could response immediately and this should have less impact on traffic.
Defining the system data model early on will clarify how data will flow among different components of the system. Also you could draw an ER diagram using the diagramming tool to enhance your design...
User
id - auto increment, PK
first_name - varchar
last_name - varchar
current_target_id - FK
current_daily_progress - FK
user_daily_target
id - auto increment, PK
user_id - User table FK
daily_target_in_minutes - integer
start_date - date
end_date - date
user_daily_progress
id - auto increment, PK
daily target id - FK
progress_date - date
current_progress_in_minutes - integer
user_activity
id - auto increment, Pk
user_id - FK
started_at - dateTime
last_recorded_at - dateTime
activity_type - enum
source - either client or third party
source_id - either the client generated id or external id, for dedup