Clean iOS Architecture pt.1: Analytics Architecture Overview

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey this is kaya from essential developer.com today we'll start a discussion on one of the most essential topics on software development they will absolutely increase your value as a software professional software architecture this is a new series where we are going to analyze and learn from different architecture ideas starting from something that every app needs analytics the material used for this video was based on John Matt and Dave both posts about different analytics approaches and I would like to start by saying thank you for sharing your experience with the community in this episode we'll show you an architect point of view with their discussion and see how we can use architecture to make better decisions enable independent deployment and development and find and fix bottlenecks and cold smells or go with architecture is to simplify our code bases and accelerate the development cycle while maintaining or core values as software developers so let's go [Music] today we'll be talking about analytics lytx is something that every app needs so it's very important that we can accommodate it easily in our apps in a couple of weeks ago some blog posts came out on the web about analytics and that started a discussion in the iOS community and some people were serious about it some people were just joking about it and there was a lot of good stuff there a bunch of different approaches some people just prefer to inline things some people prefer to use single toes some people prefer to break it down into different components some people just think like it doesn't matter any of those ideas will do a many more so what can we learn with it I want to give a different perspective how an architect would look at that and make a decision so let's dive in and show you the first blog post I started the discussion so this was a post by Jerome Sandow and it's important to start by understanding what he was trying to achieve and he wrote about it where he talks about the goals for his enum based approach so what he's looking for is he needs to be easy to log you should only need one line of code to log something from any of your controller great the system should support any underlying system great which means if you're using firebase analytics or crashlytics or if you have a custom implementation it doesn't matter it should be highly testable should be very easy to verify it we are tracking the right events they should be easy to add remove and modify events so as we add more screens or as we remove screens it should be just easy to add remove or modify events for future needs as our app is gonna be growing we're gonna have different screens different interactions different type of users so that's very important and he wants you get compile time errors whenever a call site needs to be updated great so he talks about Singleton's third-party has the case that can help you do that and finally let's talk about the architecture he chose because that's what we're talking about today so the top level API for his analytics is the analytics manager there is a class a concrete class and he wants to use dependency injection to add this to the view controllers so the view controllers can call them until it's directly great so let's add an analytics manager here great we have our analytics manager the manager talks to a non-latex engine and the engine is a protocol so we can have much more implementations great lytx engine is another type and super cost let me add this different notation to it great and then an enum since this is an enum based approach we're gonna have our analytics event types I'm gonna call this analytic event you know analytics I'm gonna use in um just make sure that we know we're talking about the enum based approach here this is pretty much data but it's very different to use enums and shrubs in an architecture point of it and we're gonna talk about why next so if you look at the code he had all the types into the enum so let's quickly add here all the types and of course we're gonna add more as needed because that's one of the goals great next in the engine protocol is gonna have one method so let's just have a notation here let's say send name at a data great that's it and he gives an example of an implementation of that engine using cloud kit great that's great here or cloud kit engine and the engine implements this protocol great so to serialize the enum into name and metadata so here in the engine this name is a string and the metadata is a dictionary of key string value string so we need a way to serialize the enum into a name in a dictionary and the way he decided to do it is to have an extension there will expose name string in a metadata string string so this is an extension great so I'm going to connect these two things so the civilization of the enum is made in this extension and this analytics manager only exists to get the enum extract the name and metadata from the extension and pass it through the engine using the log function okay so I'm gonna add a log function here where we pass this you know and this is a map layer it's gonna get an enum extract the name metadata and pass it to the protocol okay so these has a dependency here and this sends a message to the protocol so it has a dependency on the protocol as well great as you can see the dependency is also injected to the manager and here it extracts the name and metadata from the event cool now he has an example of a view controller using it message list view controller let's create this type here as well and we also gonna have a login view controller so the idea is to inject the analytics manager into all the view controllers and every time you have a new view controller or a new event you just add a new type here to the enum and then you're gonna have switch statements in the extension that creates the name and metadata then you're gonna have compile errors and you can go there and implement the name and metadata variants for the new types so let's connect here this thing has a dependency here this view controller also has a dependency in here okay and if your controller also has a dependency on the manager because if your controllers are going to create the event type and pass it to the manager so the same here let's connect so this is the dependency graph for this architecture okay so let's investigate the abstraction level of these architecture the actual go for this architecture is to separate the view controllers from concrete implementations of the engine and also abstract the engine from the events if you remember the goals we want it to be easy and be a one-liner so the view controllers can just call log and pass an event so since we have architecture it means that we can separate things in modules how can we find isolated parts so then we can understand how flexible this is for future requirements so let's think about this the enum based approach means that every time we have a new screen we're gonna add new event types here so in this case we have two screens and we already have seven different events and I would imagine that we're gonna have more screens than that in there this might be okay for our two screen apps but if I create a new old view controller here let's say my account we're gonna have to come here and change this enum and this is gonna join compiled warnings and I'm gonna have to come here and change these but I don't have to change the manager but since the manager has a dependency only enums they cannot go in isolation it has a strict dependency in the type here that's why you pass through this function if you want to create a component here let's imagine we have this box that can be a module a different project or it means we can test this in isolation this is what is isolated you can see that these box isolates the view controllers from the implementation and we can also have different implementations let's say we have a firebase analytics engine that also implements this great even change this color here so again imagine like different lamentations here so here is the obstruction we separate if you control s don't know about this implementation and implementation don't know about the view controllers but that's a bit of a lie just every time there's a new view controller or Erie learn to modify or add a new event we have to change something inside the box we need to change the enum and extension which means that there is an implicit dependency is not a type dependency so the latex manager doesn't know about the login view controller the enum doesn't know about the message list view controller type but every time I had new something here or any to change something in here about analytics I have to cross this boundary and I have to add a new type in here so this is not obstructed actually and that's why I avoid using enums to represent data like this I prefer to break it down in logging event enums message list view enums and my account view enums by bypassing this concrete enum type we can not do that so actually the line goes here because this is concrete and it's tied in to the few controllers it's not tied to the implementation of the analytics engine but is implicit dependency actually creates a dependency every time I need something new here and each read it here so there is no obstruction there and since the analytics manager has a dependency on this enum by changing these we need to recompile and redeploy analytics manager maybe that's not a problem now - because we don't have independent deployment but we have independent development we have other people working the project so as we add more things here imagine if you have 10 screens we will have 50 enum types and measure the size of these switch statement in the enum so this is a simple approach that can work in the beginning but that's gonna become a nightmare as the app grows and the app will grow right we want successful webs with users and this doesn't scale enum based approach analytics does not scale and this is gonna be kind of pain very easily so actually since the Olympics manager depends on the enum and the enum changes as the view controllers change actually the line of abstraction is here so this is one module and all of these should be bundled together to actually represent the abstraction level of these architecture so just make it this clear I'm going to put this into boxes and that's it all of this is bundled together it's a now or nothing proposition this is isolated so this module depends on this module and this entire module depends on this module as well so the actual abstraction that separates the view controllers from the engine implementations is a protocol it's this protocol and the event enum is the bottleneck here you can only add case genomes if you change the type so if you're gonna be changing this all the time you better bundle everything together otherwise it's gonna be a nightmare so the abstraction is actually in the engine which makes me think about this analytics manager if we really need it and those are two codes mails for object-oriented programming and maybe you think well I'm not using objector enter programming I use the only functions okay even if you have a functional approach to this what's gonna happen is that you're gonna have a function called log and this function also has a dependency on enum you're not gonna have analytics manager but by calling log and log has a dependency on the enum you end up in the same problem so if you think by doing a functional approach you are automatically safe from these architectural problems I'm sorry but you can end up with the same problem so some people notice this and they brought some nice replies to it and one of the proposed solution for this problem was to use a struct based approach instead of having enums that requires you to cross the boundary to add a case remove or modify something and then having compiler error that will force you to go to the extension and also edit there and have a huge switch statement by the way switch statements in object-oriented programming is a cold smell a cold smell doesn't mean that that's an actual problem it just means that maybe there's something wrong in there there are a lot of cases for switch statements but this is not a case for a switch statement because this switch happens in a dis extension so this is the bottleneck and actually the analytics manager acts as a middleman to extract something from this enum but they live in the same module so it's quite a weird abstraction here in an architecture point of view so let's look at a struct based approach that was also shared in a blog post so here it talks about the problem with just seven types this is already getting large and here is an interface there is possible for creating the event and fetching the name and the metadata and the switch or the pattern matching happens in the enum extension that lives in the same module so you're not actually switching anything you're not passing this data for someone else to switch you're just using it for a small convenience and he proposes ice-truck based approach where you create a struct with a name and metadata and you can have static functions inside the event that help you create the event with the right name and metadata you can also test this easily anywhere be adding methods here as you wanted and you're gonna see you have more liners just like you had before and for an architect there is a problem here as well so what would happen if this was a struct and now we're gonna have methods for all those things and the name and metadata actually goes into the type itself so we don't need this anymore okay so I can call the static function logging screen filled and it will create and struct with a name and metadata and I can test this as well very easily I still have one liners but every time I need something new you have to come today struct and edit so this is still leaves together all of those things still live in the same module the abstraction still lives here nothing much changed from this approach but a lot of things changed from I struct nothing stopped me from moving those methods to extensions and those extensions can leave in specific modules it doesn't need to leave in the main module close to the analytics manager which gives us much more flexibility than enums does on an enum every time you have to add a new case you have to change the main type and then fix the extension that sweeps over the cases but now since I can create this struct with a name and a metadata you also can move those methods and group them in different extensions and how would that look like I can create let's say an extension here with all the logging methods and I can create an extension here with all the My Account methods let's a change name select it and that's it great and here let's also have another extension for message all the types great so what happened now the events truck doesn't know about logging messages my account or anything it's just this bag of data that you can pass around which makes it much more flexible and convenient it can be created in different modules so we can actually put this back here and since this is all obstructed it makes sense to have it all in one module now we are back to this and this is good and we can bundle these three things separately and it can change in different pace this can be implemented by different developers at the same time without breaking the code so look at that things are contained now we can look at those black boxes and how the dependency between them works look at that this is a much better abstraction and just by changing something from enum to struct a lot of possibilities just magically showed up and we can still fulfill all that goals we still have a one-liner for invoking these every view controller has its own extension for this struct and they can leave with the same separate modules you can test these independently you still have the abstraction from the view controllers from the implementation of the engine which means we can swap this easily and we can have new view controllers without breaking this module we add new implementation without breaking the modules and that's the goal of architecture which means we can go faster we can test things in isolation we can implement things independently you can have multiple developers working in the same project without getting in each other's way you still have compiled time safeties if you're gonna write your tests here it's as easy to implement and extend so we didn't lose anything we only gained just by looking at the architecture and just by changing a single type that's how in ohms and strokes differ and for this specific case structs are much more suitable we don't even have to write any code to have this overview so if you're going to start working a feature with someone you can exchange ideas and without wasting time implementing or writing code or spiking you can write down your ideas in a visual form especially dependency diagrams and very quickly find bottlenecks of the architecture because it's very easy for us to think about our protocol and thinking all this is obstructed I can do whatever I want but sometimes that's not that easy it's not that clear it's not that straightforward what is the problem with this approach yes there are downsides one downside that I can imagine straight away is name collisions if somehow we have another screen that has something called a message or if you have multiple ways of logging in in the app and you want to separate that you're gonna start having name collisions because in an extension of a struct you cannot have two methods with the same name and if you have name collisions you can start creating standards or you're gonna start creating prefix for your methods anywhere start creating brews about it and you're gonna force people to use it and you can say if you're talking about the login view controller you better prefix with logging something logging VC let's say and the same here there will be message list field message list item deleted and items selected and it might not be a problem but you're gonna start creating these breader weird prefixes and maybe that's fine maybe you're never gonna have this problem so starting with a struct it's okay it's great so that's why this struct based approach is better than the enum approach it enables you to to easily create modules and it can add more behaviors without breaking all behaviors of adult having come by members because you can have the same safeties without having the burden of breaking calcite you'd want to break all sites you want to be able to write implementations to extend or erred behaviors without breaking the code without having to change code if you have a good architecture your code is resilient and this is the O in the solid principles is the open closed principle the code is open for extension what is closed for modification which means I don't have to cross a boundary to add new behavior I can just add a new behavior and pass it in which gives you much more flexibility and it's easier to test you have less code to write you don't have to be breaking code all the time you're gonna have less bugs it's easier to work especially with multiple developers in the same code base you can independently deploy things without breaking others code without having get conflicts all the time so this is a good go for architecture this is a good trait for an architecture and some people went further than that and said no enums and structs are too concrete there is a better way and this way was choose protocol instead of using an enum or a struct let's use a protocol so what happens when you use a protocol here let's see so let's use the same protocol notation here and still has the name in metadata properties that implementations needs to expose and instead of having extensions here now I can fix my name spacing problems right and I can have different types that implement a protocol which gives you much more flexibility I can have an enum here if I want to know I can use the old approach and use let's say this is a logging event you know so I don't have to even use logging anywhere and just say it's crazy old logging attempted log-in failed looking succeeded great I can also break these into many types I can create a screen filled event struct I can create a login failed events right and here would be a message least event struct so I can have in ohms can obstruct it can have classes you can lament this whatever way you want here you can have a screen view type that when you create this type it's gonna have a name and metadata you can say just items select it and here you can say item deleted we gives you some nice tiny api's there's still typesafe still gonna have the compile safeties so you can have everything but you have more flexibility you're not even add like more code it's the same amount of code structured into different types into different modules so this makes the code much cleaner here can account event let's say class I wouldn't make that a class but we can say screen field and change name selected great and all those things implement the protocol so in an act extra point of view now if we actually change the abstractions are is still the same we still can divide is in blocks you don't have more code you have the same amount of code but it's much more organized in two types I could even break this down into two things if I wanted let's say the message list screen events message leads actions events something like this great I have much more flexibility I can do whatever I want here I can create any structure that want from a code which means I have more control over the simplicity or the complexity of my app as well it depends how implement is but this is much more flexible and that's the goal of architecture and I don't like to make the parallels with architecture for buildings houses with code because there's something very different when you make an architecture for a house that's very rigid you're gonna build a house and it's very hard to change that texture it's very hard if you break a wall and create a bigger room or a smaller room or change their room to be something different than it was intended in the beginning but software architecture should be soft by having the right and they're standing of the dependencies in architecture it's gonna help you make your code simpler and give you the same safety's of the old abstraction so I would like to go I step further and really look at this architecture again then tender go was you have one-liners we still have that the other goal was to support any underlying system we still have that should be highly testable we still have that and it should be easy to add remove and modify events we still have that it's actually easier now and one of the goals was to get compile errors whenever a call site needs to be updated and we proved that we don't need it and actually this is a code smell that will make things harder to change because you can still have the same guarantees without breaking the internal module for analytics displacing per calls or the struct approach so I would say that by following the progression we have the inner approach there was a good approach but we made it better by using destructor approach and then we noticed that by using protocols he didn't change the architecture of the code he just enabled us to organize bed or project in two different types that implements the protocol but there is one more thing they would like to do the other thing can simplify this and the less cold smell was the middleman the analytics manager that just gets an enum extracts the name and the metadata and pass it to the engine which makes me think do we even need this class our goal was to obstruct the view controllers from the concrete implementations of the engine and protect the engine from concrete implementations of the view controllers as well and all we need here since we have our protocol it is to move the log function to the engine protocol and we can remove this not dependencies move to the engine and here is the implementation of the engine the events are still here or protocol references the event so there is a dependency here but look how all the arrows point inside and all the errors here also points inside so this is the point of abstraction now look all the errors going no errors go out of this box no errors from these two things go out of this box we means this box depends on this middle layer and these boxes depends on the middle layer box as well which means now here's the abstraction this is all we needed and that's why architecture is important we actually achieve the same thing we had before initial goals we actually made it a little bit better by following the open-closed principle and we removed code so now we have less code with a better abstraction and I think that's a win how would it look if I want to add new functionality here I can just add a method here I can add another type here I can create a nother view controller just create a different module that's completely isolated from all the other view controllers make it conform to the protocol pass the latex engine as a dependency to the view controller and that's it that's the abstraction we need to achieve those goals but there's another code smell here and that is called primitive obsession that's when you use strings and numbers to represent concepts and we're passing your dictionary string string but in my experience every time you talk with analytics and you want to measure let's say prices or time or date you cannot just pass a string every analytics use a different standard so let's say Google Analytics will require an integer to represent time but some other analytics is gonna require some ISO string version or when you're passing a currency some analytics will require a double order we require an integer so by using string strings you are not protecting yourself against that and I can represent anything for string I can pass a double as a string here the problem is that if the implementation of this engine requires a double or an integer I'm gonna have to know that here and I'm gonna have to have code like if the key price cast it to a double or cast it to an integer or two is old eight and what it's going to happen is that now we broke the abstraction that says that the engine doesn't know about the actions they actually would have to know and this happens all the time I say this like we create the right abstractions and then when we implement an engine we noticed that oh I cannot do that these specific framework or my API requires a double not a string and then you are lost anything like texture doesn't work I shouldn't do that and yeah this was caused by a premature abstraction so the next video we're gonna talk about that alright that's all for this episode I hope you enjoy it and learn something with us today and remember subtract texture includes the highest of the abstractions to the lowest of the concrete implementations so every piece of code we write may affect the system architecture by understanding these we know we are all responsible for the attack sure of the system either we care or not it has much more to cover so don't forget to subscribe thanks for watching by [Music] you
Info
Channel: Essential Developer
Views: 37,709
Rating: undefined out of 5
Keywords: ios, swift, professionalism, pairing, ios development, ios engineering, ios app development, xcode, tdd, modular design, architecture, agile, advanced ios development, tvOS, macOS, watchOS, iphone, ipad, advanced swift, clean code, unit testing, testing, best practices, swift framework, pair programming, framework, screencast, course, advanced, UI, presentation layer, solid principles, dependency inversion, clean architecture, ios analytics, analytics, enum, struct, protocol, view controller
Id: PnqJiJVc0P8
Channel Id: undefined
Length: 33min 14sec (1994 seconds)
Published: Fri Dec 29 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.