Another great video! I really enjoy your channel because you are very different from other iOS channels around youtube that only talk about how to create this and that, but never explain about the most basic things to become a good programmer. I mean, beginners should really look at your videos so they will not become the spaghetti coders, just like how I was before I discovered your channel. I'm getting more and more understanding about clean architecture and what not to dos from you. So, thank you very much for putting the efforts to create all these videos. Anyways, I read that it's better to create objects using abstract factories. From your example, can I use factories to create the LocalFeedLoader and RemoteFeedLoader? Should I create another protocol like FeedLoaderFactory and implements it on a concrete class? Is it necessary or it complicates things instead? How would you do it?
Hi Tity, thanks! We're glad the videos are helping you improve as a developer. Abstract Factories can be very helpful, but can indeed complicate our codebase. We used abstract factories in the Quiz App series ( www.essentialdeveloper.com/professional-ios-engineering-season-1 ) to decouple the Routing module from the UI module. And we compose Routing + UI in the Main module (where the factory implementation lives for now). In our case, the modular approach paid off since we wanted to have different Quiz applications and reuse as much code as we could. Every technique has a trade-off so we recommend you to use it if it'll improve your codebase, instead of using it just for the sake of using it. The problem is that we often have to make a lot of mistakes before we become good at judging when to use a technique or not. "We mostly learn by failing." And that's not a bad thing! We encourage you to try out different techniques in a safe environment, like a personal project (or even better, maybe at work you have space to do so). Don't forget to keep asking yourself "Is this approach really improving the design? Is it elegantly solving the problem? Is it making my (and my colleagues) work simpler/easier/better?" ✅⛩
Essential Developer I see. I am planning to rewatch your Quiz App tutorial because now I feel more confident about understanding it more. It used to be a little confusing for me. Thank you!
2 года назад
Definitely instructive! Should teach this at universities.
Hey Caio, Thanks for this amazing video... I have a small question... Where would we save the feed? Like how that saving mechanism would work? Where would we place the code to save the feed? In RemoteWithLocalFeedLoader or somewhere else?
Hi! It'd be in a difference component. We show it in detail in the iOS Lead Essentials program. Check it out if you're interested: iosacademy.essentialdeveloper.com/p/ios-lead-essentials/
In remotefeedloader and localfeedloader shouldnt we call completion(loaded_array_of_strings) after //do something? Because now we pass completion closure to load function but we dont execute it anywhere.
Sorry if it's a dumb question, but I want to ask you a question : Why RemoteWithLocalFallbackFeedLoader class depends on concrete type but not abstract type (FeedLoader) thus allow us to inject another implementations? Is it because RemoteWithLocalFallbackFeedLoader, RemoteLoader and LocalLoader live in the same level ?
Hello! That's a great question. In this case, the goal was to compose the remote and local loaders in a specific way: "first, try to load from remote, if it fails, load from the local cache." Thus, the RemoteWithLocalFallbackFeedLoader is a Composite implementation that enforces this specific behavior at compile time with the concrete types: `init(remote: RemoteLoader, local: LocalLoader)` This means the compiler will enforce the right composition for our use case. However, if you want to allow different types of composition, you can create a composite that depends on the abstract FeedLoader type: `init(primary: FeedLoader, fallback: FeedLoader)` This way, you can still compose a remote loader as the `primary` loader and a local loader as the `fallback` loader to achieve the behavior same behavior. But you'd also be able to compose the system in different ways, such as to "first, try to load from local cache, if it fails, load from the remote." It depends on what you're trying to achieve. The concrete option gives you composition guarantees at compile time. The abstract option doesn't, but it's more extendable. ✅
Using concrete type at this point makes more sense than an abstract one. Since in RemoteWithLocalFallbackFeedLoader, we implement specific behaviors in a specific order, forcing callers to pass exact Remote and Network Loader is a thing should be done. Imagine with `init(primary: FeedLoader, fallback: FeedLoader)`, they leave Local Loader as the primary one, the logic will be broken. On the other hand, abstract types are more extendable - I totally agree but not this case.
At 8:23 you have started to discuss a very interesting topic of possible modules structure. I wonder how would that work in the case as on the diagram because most of the time the FeedViewController with FeedLoader protocol will live in the "main module". The RemoteFeedLoader will be in the second bundle but to be able to implement FeedLoader protocol it will need to depend on the main module to use the protocol type. On the other hand "main module" will need to also add dependency of the module that contains RemoteFeedLoader. It becomes circular dependent. Do I get it correctly? To solve that problem we could move the protocol to also separated module that both main module and RemoteFeedLoader module will depend on, but seems like over complicated.
Hi Łukasz! The FeedViewController + protocol can live in the "Main module" but don't have to. We have many options. We could create a "Feed feature" module, for example. Then the API (RemoteFeedLoader) module would depend on the Feed module, and we would have the Main module as just a composer of (Feed + API) modules. If needed, we can also decouple both the Feed and API modules from each other, with an extra (tiny) adapter module in between. Or the adapters can live in the Main module. As you said, moving the protocol to a separate module is also an option, but can be overkill in many cases. There are other options, but they all depend on the needs of the system. Initially, we'd prefer to bundle all types in the same module until we have more information how to break them down into better components (depending on how they are going to be reused, for example). Although they would live initially in the same module, we'd still create a good separation of concerns with clear protocols/interfaces, so it's easy to develop, collaborate, maintain, test and break it down when needed. ✅⛩
Hi! Thanks for the detailed answer. Seems like a good topic of how to break down the app into modules aka frameworks. Most of the iOS application projects probably don't use that technique often because it seems to be too hard. The main reason is the broken part of the hard coupling of the "virtual" modules. It is very easy to do because all the types live in the same big module and this reduces the short-term duration of the task but breaks the rules which are important for the long-term goals. When you force yourself to use frameworks to break down the pieces (even separated views with features) into separated frameworks the environment forces you to think about dependencies and interfaces. I like the idea of separating "Feed feature" into the separated framework from the main module and keep the main module as a composer. On the other hand it complicates things a bit when you have a bunch of helper extensions for UIView, UIViewControllers, String and a lot more other which will need to be also moved to separated module from the "main module" to be able easily to use in "Feed feature", maybe "main module" and others. I wonder if there are more ideas that could be used here to keep the balance between usability and good separation. Btw, great video as usual!
Hi Łukasz, thanks! There are undoubtedly others options, but sadly no silver bullets. Software Development is very similar to engineering where we are constantly challenged with trade-offs. As you said, we are continually managing quality, cost, risk, etc., in the short and long-term. Even when we're unaware of that! Since we're often balancing trade-offs, the more options we have in our arsenal, the higher the chance we have to make better decisions. We wouldn't say it's inherently "hard to do," but mostly hard because it can be quite *unfamiliar* to many. And a lot of the complexity comes from the fact that our tools are not very helpful in that sense (looking at you, Xcode). Our advice to developers is to join the Mastery and Continuous Learning journey! ✅⛩
I'm a bit late to the party, but for me the big challenge in designing a good, clean architecture in Swift is error handling. For me the way the language approaches error handing is not helping with separation. For example in this app - if FeedLoader could fail - how would you approach this? On one hand you have different error scenarios when loading from local storage, but there is a completely different set when networking is involved. How do you futureproof error design so it doesn't create implicit dependencies and break open-closed? Let's say that for now if error happens the user should only get prompted with a simple alert - not super hard. But I can easily imagine that in the near future the PO will come and say "let's put an alert with additional button for networking errors". Now it's getting hard, especially when you have project with presentation and usecase separated - should we perhaps create a completely separate dataflow for errors and introduce some kind of error streams to avoid errors in application business rules? But then the problem emerges - what if in some cases error handling is not just UI, but also affects some business rules? It boggles my mind since I switched to Swift
Hi Paweł! If it's a meaningful 'Domain' error, you should probably create a rich type representation for it and pass it through the use cases and presentation. On the other hand, network errors are usually infrastructure implementation details that shouldn't leak into use cases. But that's not a problem. For example, you can have a separate pipeline/flow for those errors - or even send network errors directly to the presentation layer, which can map/translate them to view data that can be rendered on the screen (e.g., a message with 2 options).
@@EssentialDeveloper That's exactly how I implement those scenarios - only inform usecase that I/O failed and send errors in other stream. Cool to get confirmation for this approach :) Thanks for the reply :)
Kairo Can you guys create a video how to proceed to clean up the code if some one gets completely messed up code , Sort of a process which can accelerate the code clean up without affecting much with deadlines, I think this is something everybody encounters in most of the organization
Great video! Another option that could help to simplify the interface is using chain of responsibility design pattern, basically each FeedLoader can have an optional fallback loader.
Hi José, thanks! Chain of responsibility is indeed another option. We can also implement it without adding the optional-fallback to all `FeedLoader`s (we could just compose them in another type with an array of fallbacks so, every time a loader fails, we try the next one in the list until we either get a valid result or we reach the end of the array). ✅⛩
it's not clear for me, is that what you mean? protocol FeedLoader { var nextFeedLoader:FeedLoader?{get set} func loadFeed(data:(([String])->Void)) } class FeedDataSource:FeedLoader{ var nextFeedLoader: FeedLoader? var remote: RemoteFeedLoader! var isConnected:Bool{ return false } func loadFeed(data: (([String]) -> Void)) { if isConnected{ remote.loadFeed(data: data) }else{ nextFeedLoader?.loadFeed(data: data) } } }
Another example is how UIKit handle events in the responder chain. developer.apple.com/documentation/uikit/uiresponder `var next: UIResponder?` "Returns the next responder in the responder chain, or nil if there is no next responder."
Very nice, but can you guys make a video like how to make frameworks for modular approach of our application and how to handle image and fonts which is shared among different modules or framework. Second how different modules or framework will interest each other. I will be highly obliged. Thanks
in 15.17 you rewrite to load(completion) should not be completion(load), or perhas you could make a video about closure and explain why is like that instead od my understanding, even that is a great video.
Hi Yoel! `load(completion)` is the correct form since we are invoking `load` and passing a `completion closure` as a parameter. For example, it's the same as calling `remote.loadFeed(completion: completion)` - notice how we pass the `completion closure` as a parameter to the `loadFeed method`. Does it make sense now? ✅⛩
Hello! A solid line denotes a strong relationship between components, such as inheritance, aggregation, or composition. A dashed line denotes a weaker relationship between components, such as protocol/interface implementation or weak dependency. Watch this video to learn more: ruclips.net/video/Mk34R-Q9-RE/видео.html
Great video, what tool do you use to draw the diagrams?
Hi Jorge. We used draw.io
Another great video! I really enjoy your channel because you are very different from other iOS channels around youtube that only talk about how to create this and that, but never explain about the most basic things to become a good programmer. I mean, beginners should really look at your videos so they will not become the spaghetti coders, just like how I was before I discovered your channel.
I'm getting more and more understanding about clean architecture and what not to dos from you. So, thank you very much for putting the efforts to create all these videos.
Anyways, I read that it's better to create objects using abstract factories. From your example, can I use factories to create the LocalFeedLoader and RemoteFeedLoader? Should I create another protocol like FeedLoaderFactory and implements it on a concrete class? Is it necessary or it complicates things instead? How would you do it?
Hi Tity, thanks! We're glad the videos are helping you improve as a developer.
Abstract Factories can be very helpful, but can indeed complicate our codebase.
We used abstract factories in the Quiz App series ( www.essentialdeveloper.com/professional-ios-engineering-season-1 ) to decouple the Routing module from the UI module. And we compose Routing + UI in the Main module (where the factory implementation lives for now). In our case, the modular approach paid off since we wanted to have different Quiz applications and reuse as much code as we could.
Every technique has a trade-off so we recommend you to use it if it'll improve your codebase, instead of using it just for the sake of using it.
The problem is that we often have to make a lot of mistakes before we become good at judging when to use a technique or not. "We mostly learn by failing." And that's not a bad thing!
We encourage you to try out different techniques in a safe environment, like a personal project (or even better, maybe at work you have space to do so). Don't forget to keep asking yourself "Is this approach really improving the design? Is it elegantly solving the problem? Is it making my (and my colleagues) work simpler/easier/better?" ✅⛩
Essential Developer I see. I am planning to rewatch your Quiz App tutorial because now I feel more confident about understanding it more. It used to be a little confusing for me. Thank you!
Definitely instructive! Should teach this at universities.
Thanks for this example for us beginners, much appreciated.
Glad it was helpful!
Hey Caio, Thanks for this amazing video...
I have a small question... Where would we save the feed? Like how that saving mechanism would work? Where would we place the code to save the feed? In RemoteWithLocalFeedLoader or somewhere else?
Hi! It'd be in a difference component. We show it in detail in the iOS Lead Essentials program. Check it out if you're interested: iosacademy.essentialdeveloper.com/p/ios-lead-essentials/
What's the name of the UML diagram program that Caio uses?
Hi! We used draw.io
But which tool you personally used to draw the diagrams?
Hi! We used draw.io/
hi guys, Which tool do you use for draw UML diagram?
draw.io ✅
In remotefeedloader and localfeedloader shouldnt we call completion(loaded_array_of_strings) after //do something? Because now we pass completion closure to load function but we dont execute it anywhere.
Hi! Yes, of course. Calling completion is part of the unimplemented "do something" portion.
Thank you for the video! But I don't understand where should I put code to fill the DB
I always think about I need learn about how to diagrams. Thanks for this, I really enjoy this content about software architecture
Sorry if it's a dumb question, but I want to ask you a question :
Why RemoteWithLocalFallbackFeedLoader class depends on concrete type but not abstract type (FeedLoader) thus allow us to inject another implementations?
Is it because RemoteWithLocalFallbackFeedLoader, RemoteLoader and LocalLoader live in the same level ?
Hello! That's a great question.
In this case, the goal was to compose the remote and local loaders in a specific way: "first, try to load from remote, if it fails, load from the local cache."
Thus, the RemoteWithLocalFallbackFeedLoader is a Composite implementation that enforces this specific behavior at compile time with the concrete types:
`init(remote: RemoteLoader, local: LocalLoader)`
This means the compiler will enforce the right composition for our use case.
However, if you want to allow different types of composition, you can create a composite that depends on the abstract FeedLoader type:
`init(primary: FeedLoader, fallback: FeedLoader)`
This way, you can still compose a remote loader as the `primary` loader and a local loader as the `fallback` loader to achieve the behavior same behavior.
But you'd also be able to compose the system in different ways, such as to "first, try to load from local cache, if it fails, load from the remote."
It depends on what you're trying to achieve. The concrete option gives you composition guarantees at compile time. The abstract option doesn't, but it's more extendable. ✅
Using concrete type at this point makes more sense than an abstract one. Since in RemoteWithLocalFallbackFeedLoader, we implement specific behaviors in a specific order, forcing callers to pass exact Remote and Network Loader is a thing should be done.
Imagine with `init(primary: FeedLoader, fallback: FeedLoader)`, they leave Local Loader as the primary one, the logic will be broken.
On the other hand, abstract types are more extendable - I totally agree but not this case.
Yes. Got it !
At 8:23 you have started to discuss a very interesting topic of possible modules structure. I wonder how would that work in the case as on the diagram because most of the time the FeedViewController with FeedLoader protocol will live in the "main module". The RemoteFeedLoader will be in the second bundle but to be able to implement FeedLoader protocol it will need to depend on the main module to use the protocol type. On the other hand "main module" will need to also add dependency of the module that contains RemoteFeedLoader. It becomes circular dependent. Do I get it correctly? To solve that problem we could move the protocol to also separated module that both main module and RemoteFeedLoader module will depend on, but seems like over complicated.
Hi Łukasz! The FeedViewController + protocol can live in the "Main module" but don't have to. We have many options.
We could create a "Feed feature" module, for example. Then the API (RemoteFeedLoader) module would depend on the Feed module, and we would have the Main module as just a composer of (Feed + API) modules.
If needed, we can also decouple both the Feed and API modules from each other, with an extra (tiny) adapter module in between. Or the adapters can live in the Main module.
As you said, moving the protocol to a separate module is also an option, but can be overkill in many cases.
There are other options, but they all depend on the needs of the system.
Initially, we'd prefer to bundle all types in the same module until we have more information how to break them down into better components (depending on how they are going to be reused, for example). Although they would live initially in the same module, we'd still create a good separation of concerns with clear protocols/interfaces, so it's easy to develop, collaborate, maintain, test and break it down when needed. ✅⛩
Hi!
Thanks for the detailed answer. Seems like a good topic of how to break down the app into modules aka frameworks.
Most of the iOS application projects probably don't use that technique often because it seems to be too hard.
The main reason is the broken part of the hard coupling of the "virtual" modules. It is very easy to do because all the types live in the same big module and this reduces the short-term duration of the task but breaks the rules which are important for the long-term goals.
When you force yourself to use frameworks to break down the pieces (even separated views with features) into separated frameworks the environment forces you to think about dependencies and interfaces.
I like the idea of separating "Feed feature" into the separated framework from the main module and keep the main module as a composer. On the other hand it complicates things a bit when you have a bunch of helper extensions for UIView, UIViewControllers, String and a lot more other which will need to be also moved to separated module from the "main module" to be able easily to use in "Feed feature", maybe "main module" and others.
I wonder if there are more ideas that could be used here to keep the balance between usability and good separation.
Btw, great video as usual!
Hi Łukasz, thanks! There are undoubtedly others options, but sadly no silver bullets. Software Development is very similar to engineering where we are constantly challenged with trade-offs. As you said, we are continually managing quality, cost, risk, etc., in the short and long-term. Even when we're unaware of that!
Since we're often balancing trade-offs, the more options we have in our arsenal, the higher the chance we have to make better decisions.
We wouldn't say it's inherently "hard to do," but mostly hard because it can be quite *unfamiliar* to many. And a lot of the complexity comes from the fact that our tools are not very helpful in that sense (looking at you, Xcode).
Our advice to developers is to join the Mastery and Continuous Learning journey! ✅⛩
I'm a bit late to the party, but for me the big challenge in designing a good, clean architecture in Swift is error handling. For me the way the language approaches error handing is not helping with separation. For example in this app - if FeedLoader could fail - how would you approach this? On one hand you have different error scenarios when loading from local storage, but there is a completely different set when networking is involved. How do you futureproof error design so it doesn't create implicit dependencies and break open-closed? Let's say that for now if error happens the user should only get prompted with a simple alert - not super hard. But I can easily imagine that in the near future the PO will come and say "let's put an alert with additional button for networking errors". Now it's getting hard, especially when you have project with presentation and usecase separated - should we perhaps create a completely separate dataflow for errors and introduce some kind of error streams to avoid errors in application business rules? But then the problem emerges - what if in some cases error handling is not just UI, but also affects some business rules? It boggles my mind since I switched to Swift
Hi Paweł! If it's a meaningful 'Domain' error, you should probably create a rich type representation for it and pass it through the use cases and presentation.
On the other hand, network errors are usually infrastructure implementation details that shouldn't leak into use cases.
But that's not a problem. For example, you can have a separate pipeline/flow for those errors - or even send network errors directly to the presentation layer, which can map/translate them to view data that can be rendered on the screen (e.g., a message with 2 options).
@@EssentialDeveloper That's exactly how I implement those scenarios - only inform usecase that I/O failed and send errors in other stream. Cool to get confirmation for this approach :) Thanks for the reply :)
Thanks a lot!
Kairo Can you guys create a video how to proceed to clean up the code if some one gets completely messed up code , Sort of a process which can accelerate the code clean up without affecting much with deadlines, I think this is something everybody encounters in most of the organization
Hi Dipes! Do you have a messy codebase we can use as an example?
@@EssentialDeveloper NO, I am just wondering how exactly you people proceed to fix messy code base
Now I'm more comfortable with the diagrams :D
Good job, we waiting for more great videos :)
Great video!
Another option that could help to simplify the interface is using chain of responsibility design pattern, basically each FeedLoader can have an optional fallback loader.
Hi José, thanks! Chain of responsibility is indeed another option. We can also implement it without adding the optional-fallback to all `FeedLoader`s (we could just compose them in another type with an array of fallbacks so, every time a loader fails, we try the next one in the list until we either get a valid result or we reach the end of the array). ✅⛩
it's not clear for me, is that what you mean?
protocol FeedLoader {
var nextFeedLoader:FeedLoader?{get set}
func loadFeed(data:(([String])->Void))
}
class FeedDataSource:FeedLoader{
var nextFeedLoader: FeedLoader?
var remote: RemoteFeedLoader!
var isConnected:Bool{
return false
}
func loadFeed(data: (([String]) -> Void)) {
if isConnected{
remote.loadFeed(data: data)
}else{
nextFeedLoader?.loadFeed(data: data)
}
}
}
@@joserobertoabreu4877 Sounds Great, Thanks for your effort, would you give me your twitter account?
Another example is how UIKit handle events in the responder chain. developer.apple.com/documentation/uikit/uiresponder
`var next: UIResponder?` "Returns the next responder in the responder chain, or nil if there is no next responder."
Very nice, but can you guys make a video like how to make frameworks for modular approach of our application and how to handle image and fonts which is shared among different modules or framework. Second how different modules or framework will interest each other. I will be highly obliged. Thanks
Thank you for the suggestion ✅
If I could like your videos multiple times I would do it 1k times
Thank you, Josef! Glad you like our content.
in 15.17 you rewrite to load(completion) should not be completion(load), or perhas you could make a video about closure and explain why is like that instead od my understanding, even that is a great video.
Hi Yoel! `load(completion)` is the correct form since we are invoking `load` and passing a `completion closure` as a parameter.
For example, it's the same as calling `remote.loadFeed(completion: completion)` - notice how we pass the `completion closure` as a parameter to the `loadFeed method`.
Does it make sense now? ✅⛩
May I know the difference between dotted-line and solid-line in the diagram? are there any convention when to use which?
Hello! A solid line denotes a strong relationship between components, such as inheritance, aggregation, or composition.
A dashed line denotes a weaker relationship between components, such as protocol/interface implementation or weak dependency.
Watch this video to learn more: ruclips.net/video/Mk34R-Q9-RE/видео.html