Photo by Alvan Nee on Unsplash
I finished reading “Mobile System Design” by Tjeerd in ‘t Veen and I’ve got to say it’s been a great refresher and reminder of the basics of programming and coding practices in general from a design perspective.
“Consider Richer Data That Achieves The Same Result.”
This phrase stuck with me. I realized that after doing some coding, I made the mistake of not pausing enough to find a better way to accomplish the same thing, or even better.
Tjeerd mentions a case where we need to create the baseline of fetching data from an API. This API will deliver something very simple—for the sake of this example, let’s call it a Task. This Task will have some basic data like an assignee and completed. Imagine this model as if it were a sprint, so let’s establish our basic model:
struct Assignee {
let id: UUID
let name: String
}
struct Task {
let id: UUID
let assignee: Assignee
let completed: Bool
}
Nothing fancy, nothing crazy. Some of you already understand the point of the phrase that inspired this small entry on my blog. Without thinking too much about it, our model is using a Boolean to state a simple yes/no, but that is pretty much it.
We can gain so much agility, possibilities, and scalability using just another type of data that accomplishes the same thing. Again, consider richer data that achieves the same result. How about we try something like:
struct Task {
let id: UUID
let assignee: Assignee
var fulfillmentDate: Date?
}
For starters, we can add a small computed property to help us:
struct Task {
let id: UUID
let assignee: Assignee
var fulfillmentDate: Date?
var completed: Bool {
fulfillmentDate != nil
}
}
How about if we wanted to group an array of Tasks by completed date? Or how about grouping by month, day, or week? Without the Date property type change, we couldn’t do that. Yes, we can group with a Boolean completed (yes/no), but that is pretty much it. Just with a simple change to our model, we accomplished the requested need (knowing if a task is complete or not) and on top of that, we can be more ergonomic and plan for the future.
// Create some assignees first
let assignee1 = Assignee(id: UUID(), name: "Alice")
let assignee2 = Assignee(id: UUID(), name: "Bob")
let assignee3 = Assignee(id: UUID(), name: "Charlie")
let assignee4 = Assignee(id: UUID(), name: "Diana")
let tasks = [
Task(id: UUID(), assignee: assignee1, fulfillmentDate: Date(timeIntervalSince1970: 1709251200)), // March 1, 2024
Task(id: UUID(), assignee: assignee2, fulfillmentDate: Date(timeIntervalSince1970: 1709596800)), // March 5, 2024
Task(id: UUID(), assignee: assignee3, fulfillmentDate: nil), // Not completed
Task(id: UUID(), assignee: assignee1, fulfillmentDate: Date(timeIntervalSince1970: 1709251200)), // March 1, 2024
Task(id: UUID(), assignee: assignee4, fulfillmentDate: Date(timeIntervalSince1970: 1710115200)) // March 11, 2024
]
// Group completed tasks by completion date
let completedTasksByDate = Dictionary(grouping: tasks.compactMap { task in
guard let date = task.fulfillmentDate else { return nil }
return (date: Calendar.current.startOfDay(for: date), task: task)
}) { $0.date }
.mapValues { $0.map { $0.task } }
// Result: [March 1: [Alice's task, Alice's second task], March 5: [Bob's task], March 11: [Diana's task]]
// Group by month
let tasksByMonth = Dictionary(grouping: tasks.compactMap { task in
guard let date = task.fulfillmentDate else { return nil }
let monthComponent = Calendar.current.dateComponents([.year, .month], from: date)
return (month: Calendar.current.date(from: monthComponent)!, task: task)
}) { $0.month }
.mapValues { $0.map { $0.task } }
// Result: [March 2024: [Alice's task, Bob's task, Alice's second task, Diana's task]]
// Simple grouping: completed vs pending
let tasksByStatus = Dictionary(grouping: tasks) { $0.completed ? "completed" : "pending" }
// Result: ["completed": [Alice's task, Bob's task, Alice's second task, Diana's task], "pending": [Charlie's task]]
Of course, this is taking into consideration that we’re still in the process of defining the API of the project and we can do some collaboration with the backend team.
I guess reading this reminded me just to take some time, as we should, to think about the way we are designing something and how we can create richer capabilities in our code, accomplishing the base needs but on top of that having new possibilities for the future.
This post is a little shorter and simpler, but I really wanted to talk about something that I’m sure most of you already always have in mind, but I’m sure sometimes I forget to remind myself more often.
Do you think there is other ways to handle your data but achieves the same result? Let me know what you think.
Subscribe to My Newsletter
Get updates about coding, Swift development, and occasionally some dog stories!