From what I gather, you're adding objects primarily for Separation of Concerns.

Okay... I get that. But adding another object means that I now have to write code that maps data to and from the model to the domain object. So the question here is that did the benefits of the separation outweigh the disadvantages of writing more code? Code that could, in fact introduce its own set of bugs into the project.

As I tend to hate writing boilerplate code, my initial response to that question is no, it does not.

Second, another stated reason for introducing said objects lies in the possibility that these objects could change in the future, hence isolation is needed.

But now I think we may be facing conflicting rules. Here we're placing Separation of Concerns higher than another rule, that of Avoding Premation Optimization. You're writing more code now to avoid potential problems in the future.... that may never occur.

If a repository provides ToDo objects today, then it could in the future still provide ToDo objects to the rest of the system even if the datasource format changed. If the change was simple, say, a field name changed, we could simply use coding keys to map the new name to the old name expected by the rest of the system.

If the changes are complex... okay, fine. It's just that THEN we map the changes from and to our new DTO objects to the existing ToDo objects, which NOW becomes a full-fledged domain object.

And all that said, my biggest problem here is that this approach isn't very Swift-like. If I knew I had a high likelihood of changes, or I definitely knew I needed to map disparate datasources, or if I definitely wanted to hide infromation from the rest of the app, then I'd probably avoid still avoid domain objects and instead choose to wrap things up in a protocol.

protocol ToDoType {

var userId: Int { get }

var id: Int { get }

var title: String { get set }

var completed: Bool { get set }

}

struct ToDo: Codable, ToDoType {

let userId: Int

let id: Int

var title: String

var completed: Bool

}

If my repository returns ToDoType, then I'm gold and furture-proofed. Futher, ToDoType clearly specifies my contract as to what information is mutable and what information is not. (So do the vars on the original type, for that matter, but that's an entirely different article.)

Further, protocols have the ability to wrap and provide getters/setters to very complex objects, should that ever be an issue.

Large, complex objects also brings us into the data minimization issue, which again in my book is a non-starter unless and until memory preasure becomes an issue due to the fact that you're managing tens of thousands of objects. A dozen or a hundred? Not so much.

Not to mention that, should you ever need to send an updated version of the object back to the API, you're probably still going to need all of the hidden data anyway.

As much regard as I have for Martin, one needs to keep in mind that a lot of what he says is BS (Before Swift). ;)

In Swift, protocols and value-type behaviors go a long way towards adressing traditional shared object and data isolation concerns.

Thanks for a thought-provoking article.

I write about Apple, Swift, and technology. I’m a Lead iOS engineer at CRi Solutions, a leader in cutting edge mobile corporate and financial applications.

Love podcasts or audiobooks? Learn on the go with our new app.