What's up everybody, welcome to another episode of the podcast. This week we are going to be talking about a follow-up here to a blog post by Jeff. He was talking about Swift data and separation of concerns came into play and we thought, you know what, this will make a good episode. So let's start by saying, Jeff, how are you doing buddy? How's your week been?
I'm doing great. A little behind the scenes. We are actually recording this episode like three days after the last episode. So there actually hasn't been a whole lot of time for anything to happen. Yeah, spoiler alert. Right. Yeah.
Yeah, nothing got better in three days, right? That's basically what we're saying. All right, so let's get into this. So you did a blog post that you put out there. And of course, folks, we'll put a link in the show notes. And, you know, for those who don't know what separations or concerns are, I think this is going to be interesting and probably spark some conversations, right? Based on what I saw in the Discord. And again, we'll put a link in the show notes. So go for it, Jeff.
So yeah, this all came about. The blog post was kind of heavily demanded by people in the Discord because I had spent a rather decent amount of time railing against the query property wrapper in Swift data. And people were really like, hey, you know, if you hate query so much, like why do you hate query so much? Explain why you hate it, why you think it's bad, yada, yada, yada.
And so out of that came a blog post that was essentially, you know, a little over the top opinionated, but you know, that's, that's how you do these days. Right. Yeah. That's what makes us tune in for us to be opinionated. They don't care about the truth. Yeah.
Basically saying, hey, don't use the query property wrapper in SwiftData because it makes your code more fragile. It makes your code less flexible. And it's easier to work with SwiftData if you're not using it. And the reason for that is the idea of this separation of concerns.
And so I think what we're going to do is we're going to pull out the bits of that that are generic, the bits of that blog post that apply no matter what language you use, no matter what frameworks you're using, and really kind of talk about, like, why does this matter? How can you implement it? And what are the benefits you get from implementing something like this? Yeah. Now, first of all, I do want to say, right, I use that query in my Swift data.
Not that I've extensively used Swift data, and I've not had a problem. So, you know, I guess I'll be on the other side of the fence. Have you shipped an app with it? That was a good question, actually, because I was thinking about that today. Have you actually finished shipping? Did I? This came up actually the other weekend because someone asked me about Job Finder Tracker. And Job Finder Tracker does actually use some Swift data. So, yes, I guess the answer is yes, I have. And I have successfully sold...
to someone, some people's. So I guess it's okay. Yeah. Yeah. No, I mean, if it's not like if you are using query right now and you, you're not having any problems with it, you got to go out and tear it all pieces right now. But, um, I definitely think it encourages some bad behaviors and I think it should more or less be avoided, even though Apple really kind of tries to push it because it makes their code look pretty on WWDC slides. Yeah.
It's all about looking pretty. I think what I'm going to do here on this episode is kind of provide like a sample example, one that I can discuss in audio form and kind of work through and explain what you might have done before, why this can cause problems and how you can avoid these problems by using the principle of separation of concerns and some of the benefits that you're going to get through doing that.
For this example, let's imagine you've got your standard default. What's a project that you're going to build? And we're going to talk about a task list app. And in this task list app, you are syncing this with some kind of remote API. Could be a server, could be iCloud, could be whatever. We don't care. Now, what you would do, like kind of the simplest, most straightforward approach that you're going to do,
is you're going to write some kind of type that represents, say, your task. And with that type, you're going to say, okay, in Swift, you might make it codable. In Android, sorry, Kotlin, what's the language that thing's called? No.
In Kotlin, you might have the Kotlin serialization and mark everything with the serializable, encode and decode them from your API. And then you might use that same type directly in your view. You're going to say, okay, well, I've got a list of tasks. I'm just going to take these tasks that I got from the API, and I'm just going to put them in a list. And that works great once. You kind of just go fetch that data from the API. Here it is displayed in the list. Not a problem.
Well, imagine that you as a developer don't control the API or you do control the API, but you can't, you know, you're a mobile app developer. You can't deploy them at the exact same time the way you can with a front end web app or anything like that. And now the API has to change for one reason or another. And if you think this doesn't happen in the real world, you would be wrong because this happens all the time. I was going to say this, this actually for me may be the one thing right here because this happens all the time. And anyone that,
has ever said, oh, this will never happen. Life will be fine. Or if some API builder promises you it won't change, run, run for the hills. And so, yeah, this has been something that I've seen at a number of different jobs where it's like, okay, the documentation said this value will never be null. And all of a sudden it's null. And then your app is throwing errors, crashing,
Silently failing whatever, depending on what exactly your language is and exactly how type strict it is, that kind of thing. But yeah, imagine that the API has to change and now a field is nullable when it was not previously nullable.
So now, okay, fine. You've got to go through there and it's like, okay, I marked this field nullable. It's now decoding properly. But now everywhere in my view, I have to handle this case that it might be null. And now I've got to change every single view where I've displayed this task to make sure that this value is not null. Or if it is null, that I'm doing something with it. You know, I'm converting it to an empty string. I'm doing something like that. And this is the problem is that you've now had...
things that are entirely part of the API contract now kind of affect how you build your views. The solution to this is the term that I've been using throughout this episode, and that's separation of concerns. Now, separation of concerns, it's an old, old, old principle. It was first coined in 1974 by a very prolific computer scientist named Edgar Jijkstra. I'm sure I mangled that name.
It's close enough. And it's in the show notes because there's no way I'm going to try and pronounce it. We will link to the Wikipedia page for Separation of Concerns. It's got his name on it. Don't blame us. Yeah. If you've seen the name, you know who we're talking about. Anyway, the idea that he kind of put forward is that each –
Piece of code and piece of code is a little bit nebulous, but we'll just come back to that should really only have one thing that it's concerned about is the separation of concerns. And if you have multiple concerns, you should separate them into multiple pieces of code.
And that can happen at a couple different levels. You can have that, you know, like, oh, I've got multiple types. I've got, you know, different classes. I've got different structs. I've got different whatever. Or it can, like, level up and you can talk about it at, like, a process level. That's the whole Unix philosophy that you've got individual small programs and they all do something well. And so you can, like, pipe them all together on the command line. But that's the general idea is that...
Something has one thing that it does, and it does that thing well, and it's not concerned with other things. Some other people may recognize this as the S in solid. That's single responsibility, but Jack Strick got there first.
So yeah, so what we're seeing is our code is not following this principle. It is concerned with two different things. It is concerned with how it's represented on the server. It's your API contract. And it's concerned with how this data is going to be displayed in your front end, in your view. And that's really already causing us problems because if one of those things changes, now necessarily the other thing also has to change.
And so the solution is let's separate those into two different types. And the terms that I'm going to use for these types right now, they're not necessarily universal terms, but I'm going to use the terms data models. You may also hear these called DTOs or data transfer objects. It's not necessarily the case that they have to be objects in every single language. That's why I'm using the term data models.
and domain models. And there are a specific subset of domain models that you've probably heard of later. We'll get into that. But those are the terms that I'm going to use right here. Data model, this is what you use to pass data around. And domain models, this is the version of it that you're using in your app because it is specific to your app's business logic.
Any questions so far? No, no. I mean, I agree with everything so far that you've said. Yeah. Hey, folks, if you like what you're hearing in this podcast and you want to help this podcast to continue going forward and having great guests and great conversations, I invite you to become a Patreon supporter. You can go to patreon.com forward slash compile swift, where you will get ad free versions of the podcast along with other content.
Okay, so let's start with the data model, the DTO. In my mind, this should ideally encode your API contract and no more, no less. Obviously, that's not always 100% going to be the case, but that is the ideal. That really all you're doing is you are kind of just saying, this is what I am speaking to the API. And really, I don't want to be any more complicated than that.
There's this idea out there in computer science called Postal's Law. It's also called the robustness principle, which kind of goes by the wording, be conservative in what you send, be liberal in what you accept. That is, you know,
Try to take in whatever you can without throwing an error, without really having any problems, and try to limit what it is that you're actually sending out. Take in everything that you can without errors, without any other problems, and kind of limit what it is that you're sending. You be consistent even if what you're receiving is not consistent. And so to that end, I think that you want to have your data models kind of be at
as accepting as possible. In a lot of cases, I think unless you are absolutely 100% certain that it absolutely never, ever, ever can have
a nullable there, you should just default to all of your fields except null. That's fine. Just go ahead and accept whatever you can because that way if something suddenly starts becoming null, you're not suddenly failing decoding because you didn't expect that. Okay. I do want to jump in there. Just to clarify though, just want to make sure I'm understanding what you're saying because I'm always drumming into folks
you know, allow for the outcome to be something other than you expect, right? Of which a null is a perfect example of that, right? What just because someone tells you, look, this piece of data will always be there for you. That is the first sign to say, I'm going to code
for when it's not there because they more than the more they promise it'll be there. The more I tell myself it won't be one day. Right. So, you know, make sure you cover your butt on that. Right. The other thing I think that you should do in accepting things as they are from the API contract is don't have any sort of type that you're decoding to that is not something that your transfer protocol requires.
can represent. So let's use the example of JSON. Everybody knows JSON. Everybody knows what's in JSON. You've got objects, you've got arrays, you've got numbers, you've got strings, you've got whatever. What you don't have in JSON is dates. That's not a thing. You can represent that as, say, a number that's your Unix setback. You can have a string that's like an ISO 8601 string, that kind of thing. Don't try to encode in your API contract
the idea of a date. Just take in a string if that's what your system is going to send you. If it's going to send you a string, keep it as a string. I know that some languages, again, we're going to go back to Swift, that's kind of our bread and butter, have things like, oh, well, you know, you can put a date in your codable struct and you can have some setup there that go ahead and decodes it for you on the fly. Don't use those. You should take in a string. If it's a string,
We'll figure out whether or not it can be converted to a date later. Accept what your API can give you. Don't accept anything else. I very much agree with that because, okay, I get it. Folks have varying opinions on this for sure, right? But I always, again, I think to myself, look, if something's coming in, the most –
If something's coming in, the easiest thing to always handle is going to be a string, right? Get your string, convert it, or check that you can convert it to what you need. But just by sending string back and forth simplifies the whole thing, right? The amount of times that I've spoke, you know, had conversations with API engineers, and it's like, look, I want a date. And it's like, well, we're going to send you a string. Oh, but that means I've got to convert it. Well, that's a good thing. It means I can test that.
And ensure that this string will convert to some kind of sensible date, right? Yep.
We have a person in the chat right there saying one other one that I forgot. In a lot of cases, your API contract will specify a kind of pseudo enumeration type where it's just like, oh, it can be one of these three values. And in a lot of languages, you have this idea of like on the fly decoding, you're going to say, this is the enum value that it can be. But what that actually is in the JSON is just a string again.
Take that as a string now. You're going to run into a point where, oh, you had handled the three enum types that you had, and now the API is sending a fourth enum type, and now your decoding is broken. Don't do it. Take it in as a string. You can always convert it later. We're going to get to that point later in the podcast. Your data contract, your data type there, it should handle a string. Done.
Agreed. Yep. And let me say, because I know folks out there who know me, they're going to say, but Peter, you're always saying how much you hate using strings for things like string matching and so on and so on. Yes, that is true. But this is a different thing we're talking about here, right? This data coming in needs to be as simple as it can be. And I don't think you get simpler than a string. Yeah. And we have other options coming up. So let's talk about those now.
The other side of this is your domain models.
In your domain models, you can do all of these things. This is what you want here. This is a representation suitable for your app. This is what you're going to use in your app. This is how you're going to pass data around throughout your app. So you can have the things that you want. You can have non-optional values. So you know that like, if I'm dealing with this at all, it's not optional. It's going to be there. We've guaranteed in the type system that it is there. You can have these more complicated representations such as dates. You can say like,
okay, this is a real date. This has a time zone associated with it. We can do date math on it. We can do whatever we want. It's handled there. You can have your enums so that you're not doing the stringly typed stuff that Peter was just railing about where you're comparing strings or you're comparing any of the other things. You can have all of these nice things inside of your app and they're not
connecting to the API. They're not part of your API contract. Now, the version of domain models that I think a lot of people are going to be familiar with and may not have recognized this quite yet, but we'll click as soon as I say it,
One of the most common types of domain models are view models. So this is a case where you've got a version of data as it needs to be represented in the app, and it is explicitly set up in a way that it is meant to be displayed at this point. That is your model as it is done for a view model. Fairly straightforward name there. Yeah.
And I think in a lot of simple apps, this may be all you need. You need your data model and you need your view model. And those two things are your connections. I think the reason that I use the term domain model here is that I think it's a little bit limiting for more complicated apps to say that those are your only two models. And that in a lot of cases, it almost makes sense to have an extra layer there where you have your data model, you have your
internal business logic domain model, and you have your view model and you kind of map between all, well, you kind of go through the center of domain model. And so you can go from data model to domain model. You can go from domain model to view model. You can go from view model to domain model. You can go from data to domain, but you don't ever go from view to data. You always go through that central domain model.
I do want to jump in and point that out because it kills me that I still come across folks who will take that incoming data, push it straight into a view, do whatever, and send it straight back out through the data again. And it drives me crazy. Not the good way to go, folks, right? Just like we were saying those APIs change. Well, if that data changes, you got a whole bunch of rework. And I just think it's a bad practice, right?
to not have that middleware layer with the view models. That's just my two cents. So let's talk about the implementation. How would you actually go about doing that? How do you actually go about maintaining these two separate models? So I think the core of what you have in your app is you have one
that is responsible for actually doing the communication with your API. And one thing to look up that may not be the solution for everybody, but is definitely a good starting point, is the idea of the repository pattern. And that is basically a way of saying, like, I have one thing that handles communicating with my data and...
What I send to and receive from that is just my domain model. The repository internal to itself handles the data model and it references the data model because it needs to talk to the API. But outside of the repository, you should never see that data model. You're only dealing with the domain model at that point. And the way that you handle that is through the use of data mappers.
What those are are specific types for saying... And data mappers are for...
converting between your data type and your domain type and vice versa. And really what they're there for is for handling all the complexity there and stuff like, okay, we have this date as a string. We need it as an actual date. Oh, we can't parse it. Now we're able to say, here is the specific error. Here's the specific object. Here's the specific cause of this failing. And so all of the complexity of error reporting is there in your data mapper. Your data mapper can really like
do all of whatever complicated finagling it needs to do. Your data mapper can handle, oh, we've got like multiple different versions of the API. If we see version one of this thing, well, it's...
you know, ISO 8601 string. If we see version two of this, it's, oh, it's got, you know, a time zone associated with it where it didn't before that kind of thing. All of that complexity goes into your data mapper type. That way you don't have to have it in your domain model or in your data model.
You don't have to have it in your repository. Everything's just kind of centralized. This is what converts from this thing to the other thing. Time for a break. Hey, everybody. It's Peter Whittem here from the Compile Swift podcast. I want to tell you about Setapp. Setapp is a service that provides a subscription fee of just $10 a month, and you get access to over 200 Mac applications. And it's also available now on iOS as part of that deal.
I use the service because it just has a ton of really good first-rate apps that I use all the time. For me, it's invaluable as a developer to have access to tools for things like APIs, for planning projects, writing emails, writing documentation, and you get all of these things, including database apps, all of that kind of stuff right there on the Setapps service for just $10 a month. You can use as many or as few applications as you need.
If you're interested in checking this out, go to peterwhitam.com, P-E-T-E-R-W-I-T-H-A-M.com forward slash set app, S-E-T-A-P-P. And you can see the details there. And it's got a link that you can go over and start using the service and see how it works out for you. I strongly recommend this to every Mac user. Break time over.
Okay, great. Now, everything you have said so far, this may be a unique episode because I completely agree with you. So write that on the calendar, folks. All right? It's not going to happen very often. But one of the things that I think is a good example here is testing your code. And like you said, like finding bugs. And also when the...
when the APIs change, when your business logic changes, all of these things, these separation of concerns are a great way to say, great, I'm only rewriting this small chunk of my code. I'm not rewriting some massive refactoring of my app, right? But I think that one of the important ones is it does make it, in my opinion, very testable. And hey, that should be a huge benefit right there. So let's talk about some of the benefits of doing this
offset again, because I'm sure there are folks that are like, oh, but this is so much work. So let's talk about the benefits of why this is important. With each of these pieces kind of handling just a small section of it, it makes it much easier to test because you don't have to build up this kind of like full pipeline. So for example, I just discussed these data mappers.
So having something where you were previously saying, okay, you know, if I've got this version of this, I need this. If I've got this version of it, I need this. That's going to be really complicated if what you're dealing with is like your end view. You're going to have to –
pass a bunch of stuff to it and make sure that you've got like just data. And I've seen so many things where people are just like, Oh, we're mocking out our entire networking stack because you know, we're, we're getting rid of, of everything because you know, at the end of view, like we need to be able to say what the Jason was. And it's like, this is, this is too much. Like, or what's worse is people are just saying that's too much work. We're just not going to do it.
Whereas with this, where it's like, okay, you've got one small part of this and you can say, I am passing this very specific data into here and I expect this very specific data back out. It makes it very easy to test. It also means that you have, like we said earlier, the ability to handle changes without having to really
have that cascade between the rest of your app. So that's the small changes that we discussed, like, you know, nullable fields or non-nullable fields being suddenly becoming nullable.
Things like that. But also like I've seen just gigantic migrations that went awry because of a lack of separation of concerns. You know, at a past job, we had a case where the entire app was built on gRPC and protocol buffers. I'm not going to explain what those are, but just generally like that's the way that we communicated with our API and
And then it went to GraphQL and we were suddenly speaking GraphQL.
Now, the problem was all of our gRPC objects were being used directly in the UI. So what does that mean when we switch to GraphQL? Well, that means not only do we have to change our networking layer, we have to change every single piece of our code to make sure that it's now handling this new GraphQL. And if I recall correctly, like it didn't even like all move over at once. So it was just like, oh, we've got like,
protocol buffers over here and we've got GraphQL over here and they don't really communicate with each other. And it was just a nightmare. And, and this is a case where having that all kind of separated away in the, the first place would have saved us a ton of headaches. Yeah. If this was something where it's like, once you hit that repository layer,
You don't have to worry about whether it's protocol buffers or GraphQL anymore. That would have been great, especially in like a larger company where you may have separate teams. You've got like a platform team. You've got feature teams dedicated to specific parts of the app. Why do your feature teams need to care about what's going on with the network? Like that shouldn't matter. They should be able to build that kind of stuff without it. And then to that, to that same extent of like the separation of teams, like,
If you are building something like this and you know that you're able to rely on some kind of internal business tool, what that means as well is that you as the front end team, you as the mobile app team, you as the whatever team, you can go ahead and start building your features to these internal types without the backend being available yet. That was another thing that I've seen a lot in a lot of different companies is people going, well...
we need to build this feature, but we can't build this feature yet because the backend team, they haven't finished the backend yet. And, you know, it's great to use that as an excuse when you don't want to actually do anything, but you can actually build these things. You like, there's no reason why you necessarily need to wait on the backend team for something like that until what you're building is that data mapper, that repository side, you can build out your entire feature against these internal domain models and then build
All you need the back end for is that conversion between the two. And you can go ahead and be on your merry way without having to be blocked by this other team. I would like to say, and I get it, maybe it's controversial, but hey, you know, I think that if you find that you are saying, I can't do, you know, let's just take a date, right? Okay, I can't do this, build this part of the app yet because I don't have this
The data, this date that I need to put on here and see how it's going to look, that should be your number one sign you have not thought this through or you are doing it wrong because...
What's the process equivalent of a code smell? Exactly right. You know, because I agree with you completely. Yes. Okay. There is a benefit to having everything you need at a certain point as early as possible so that you can code and test and everything else. But if you are in some way saying you are dependent upon that to build out part X of the app, then
I would go so far as to say, well, then you're probably the problem, not where the date's coming from, right? I mean, I had a past experience at a company where the backend that we were waiting for was a piece of hardware. Like we were communicating with a piece of hardware that had to be manufactured and prototyped and all of that. And it's like, if we had sat there the entire time and said, well, we actually can't build any of the app yet because we need this hardware to happen. Like,
we wouldn't have had a job for six months. Yeah. And so, yeah, you need the ability to kind of have internal things that you can deal with and then be able to just mock out just that last little bit. You're like, I know that I need this feature. I'm going to need this data. And then like, you can build your domain model that has the data that you need and work with that. And then it's like, okay, at some point,
probably before the backend is actually like up and running, then you get the spec doc and then you can kind of start building the mappers together. The, you know, you'll have your mapper, like you can build the mapper and you can go like, okay, this is what we know the API contract is going to be. This is going to be with that. And then like that last little bit is just like the URL session connection, like just hook it up to, uh, the actual network. Um,
But yeah, like, cause you can, you can have different levels of that. You can have your repository just vends the, the, the final result to you. That's normally what I do when I need to mock out the repository is I just say like, okay, it just does this, but you can also mock out like the network layer above the repository and say like, okay, this network layer is vending me.
The data models. And then that's going through my repository and that's going through the mappers and then it's going through the domain model. But honestly, most of the time I just, I just mock out the repository and say, yeah, just go ahead and return these domain models. And I kind of skipped that other little bit.
So yeah, I think that's basically all I have to say about this for now. There's a lot of benefits you get from this. It does take some extra work up front, but I think it allows you to be much more explicit about everything that you're trying to do. And it obviously has a lot of these upfront benefits. So I definitely recommend...
definitely look at the links that we've included, do some more in-depth reading about this, but really start to think about this the next time that you're building out any of these kinds of things in your app. And it really makes it
So that it's easier to continue to expand, to continue to test, to react to changes, unexpected changes in your back end. You get a lot of nice wins from this. And so I definitely recommend taking a look, see where you can improve your apps that way.
Yeah, and I think it's, I want to point out here, I personally believe it is better to spend this time thinking about these things up front and avoid incurring that technical debt later on when you have to deal with these problems because you didn't go through this process. And I think that when you are designing your app, and by designing, I don't mean the UI and everything. I mean, when you're architecting it and that,
Have these at the forefront of your mind, right? This is what this looks like now. What if this changes next week? Because as we said earlier, next week, if you've ever worked with teams in a company and big products or things like that, next week always happens when they change it. Or six months from now when they pivot to some new thing or you get bought out and it becomes something else.
So plan for it now, right? Stop the technical debt before it happens. It's not going to completely solve all your problems, but it'll compartmentalize some of your problems with smaller refactoring. That's a great way of putting it. It compartmentalizes your problems. Yeah.
When you have problems, and you will have problems, that's the nature of software development, you limit the area of impact that you have. If something changes, you really have this firewall of the repository and saying, it's not going to spread beyond this because...
Everything else, like it's cut off from that. That is your break point. And you're not really going to have to re-architect your entire app because one other thing changed. Yeah, agreed. And also, as we know, developers, hey, they hate having to go back and redo things. They like to do new things and cool things. Well, if you do this, guess what? You're going to be going back less and spending time on this.
All right, Jeff. Well, thank you so much for this. Folks, we will put links in the show notes to the article. Go read it and keep it at the forefront of your mind. Jeff, where can they find you? You can find everything I do at Cocotype.com, even though that's not actually where the blog post is this week. There's a link to it from Cocotype.com. Yeah. Cocotype.com is the Jeff starting point, right?
And for me, the starting point is PeterWhitam.com. Folks, if you like what you're hearing here, right, come and join us on our Dev Club Discord. Again, there'll be links in the show notes for this. This is the kind of stuff we talk about this a lot. We talk about many different topics related to development. But these are the key areas that come up a lot. And, you know, we get asked a lot of these questions. And...
going in our discord where these conversations are taking place not only do you get to be part of it but there's all this fantastic reference you can cause it yeah you can cause these problems yeah cool cool cool for the infighting about compartmentalization right um but that's it folks that's what we got for you i hope this has been helpful uh let us know leave us a review if you want to go the extra step hey there's the link for the patreon we greatly appreciate that thank you folks we will see you in the next episode