© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
A. TsadokMastering Swift Package Managerhttps://doi.org/10.1007/978-1-4842-7049-3_2

2. Organized Mess

Avi Tsadok1  
(1)
Tel Mond, Israel
 

Every program attempts to expand until it can read mail. Those programs which cannot so expand are replaced by ones which can.

—Jamie Zawinski

Before we run forward and talk about Swift Packages, frameworks, and libraries, we need to prepare our app for that.

In the bottom line, packages are just another technical expression of good architecture and reusable code. It is almost inevitable to organize your project if you want to implement Swift Packages.

In this chapter, you will
  • Meet “Weathery,” an imaginary project that we follow in this book

  • Learn what the separation of concerns principle is when talking about architecture

  • Learn what the three-layer architecture is and what its benefits are

  • Understand how to redesign our app by using a UML

  • Learn what closed/opened layers, design patterns, entities, and interfaces are

Meet “Weathery”

It was a lovely day at “Weather Apps,” the company behind “Weathery,” the App Store’s best weather app.

“Weathery” is a long-existing app and has millions of downloads.

Even though a weather app sounds like a small project, it has a varied list of features, such as
  • Authentication flow, with registration, login, and forgot password feature

  • A search engine

  • Location-aware features

  • In-app purchase

  • Beautiful screens that show weather in various places in the world

  • A user configuration page

  • Analytics

Other than that, the “Weathery” iOS team is adding more features and bug fixes daily, trying to keep the app stable and agile and keep pace with its competitors.

“Emily, can you come for a second? I need to talk with you about the direction we want to take” – a sound was heard in the open space out of the VP Product mouth.

Emily was the iOS tech lead of the app for the last 3 years. She knew every single line of code and was in charge of the most advanced features the app has.

“We see an increased demand for an Apple Watch version . Many users ask for that. We want to take the current version and build a dedicated Apple Watch version. The plan is to do the same for macOS in the future, but as I said, we want to start with the Watch first. What do you say? How easy is it? We already have the logic written, don’t we?”

The Weathery Project

To understand if Emily can try to use the already written logic for the Apple Watch, we need to examine the Weathery project. Let’s have a look at the project navigator (Figure 2-1).
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig1_HTML.jpg
Figure 2-1

“Weathery” project navigator

By quickly going over the existing folders, we can see a few things:
  • The iOS team organized some of the files by their operational part. We can see a dedicated folder for view controllers, another one for views, and so on.

  • Regardless of the previous point, we can also see different services – we see folders for a network, analytics, and persistent store.

  • Since we don’t see any business logic folders, we assume the view controllers hold most of the app’s logic.

This may look like a big mess, but we do see a pattern here. The Weathery team organized (Figure 2-2) their code.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig2_HTML.jpg
Figure 2-2

Weathery project organization

This may be an intuitive way to organize files, but not always the efficient one. Regarding the new mission to develop an Apple Watch version, managing your project can make it challenging to maintain it over time. Emily needs to rethink about reorganizing its project in a different way, a more efficient one.

Code Organization

We can’t say the Weathery project code is not organized, but we indeed have a better way to manage our code and make it more reusable and stable. To reveal how to do that, we need to understand a few essential principles, some of them are probably familiar to you.

Note

Right now, I don’t want to talk about any technical terms such as frameworks, libraries, and modules. We need to understand the theory first.

Separation of Concerns

Separation of concerns (SoC) is one of the most crucial principles when talking about software design. SoC means two things:
  • Each component, a function, or a class has only one responsibility.

  • Two components or two functions cannot share the same responsibility.

Separation of concerns is a key component when talking about a modular app. We already know we should write small functions that their goal is to do one task only. We also realize that’s the case with classes. We don’t always understand that this principle is also valid in a full module or a library.

One of the best ways to assimilate this is to move out of the computer science world and go to real life. It may sound weird, but yes, SoC is also valid in examples from the real world. In fact, it’s vital for planning many projects and infrastructures.

Let’s take a standard house and try to break it to different components with different responsibilities.

So, we’ve got a house – the house provides us a place to stay and live. We don’t plan a house to be a public place, an office, or a shop – the house has only one mission. Inside the house, we have rooms – each room has its own responsibility. The bathroom is where we clean and wash – after all, we don’t have a bed in our bathroom. The bedroom is the place for our private tasks – sleeping and changing clothes.

Every detail, every tool, and every furniture are there to support this fact.

But it doesn’t stop in the “rooms” level. We can continue to “break” our house to more subcomponents – the sink in the bathroom is for washing our hands and face, not for dishes, and the closet in our bedroom is for clothes, not for our working tools.

What will happen if we try to give a component in our house more than one responsibility? What if we want to cook in our bathroom? Or sleep in the kitchen? Obviously, it isn’t a good idea. Each room has its mission, a goal, and it is the best one in doing that specific task. We can describe the house structure as in Figure 2-3.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig3_HTML.jpg
Figure 2-3

A house diagram

So, we can apply the same logic from our house structure to program design. Our program/app is built out of components that have their own responsibility, and they are themselves can be a part of a bigger component that also has a bigger task.

But where do we start? What floors come first? Or, maybe the question is – how many floors do we have in our app?

Presentation, Business, and Services

Now that we understand what separation of concerns is, let’s try to look at our app from a bird’s-eye view and understand the primary components our app is built on top of.

Some developers try to separate their app to features – login/registration, main screen, settings screen, search, and so on.

While it seems reasonable, it doesn’t really help us to scale and maintain our project in the long run. First, our app can have dozens of features, and we can lose control of our architecture. Second, surely some of the features will have to share some core components. Persistent store and network are easy to talk about, but what about business logic or other services? Coupling everything into a “feature” does not make it easier. And third, what exactly is a “feature”? Is it a screen? Or maybe it’s an app capability like “sharing” or “in-app purchase”?

A “feature” is something hard to define precisely. Therefore, it might not be the right candidate for separation.

It All Starts with Data Flow

But maybe, there is a more logical way to look at our app, for example, through its data flow. Now, data flow doesn’t mean music streaming or file download. Data flow is the path for the information to move from one part of the app to another.

Let’s examine the “tapping on a button” action:
  • The user taps a button.

  • The view controller passes the event to the view model.

  • The view model prepares the model and passes it to some class that decides what to do with the new information.

  • From this point, the data can go to the core data or to our server using the URLSession.

Let’s try to look at it in Figure 2-4, with more examples.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig4_HTML.jpg
Figure 2-4

Data flow diagram

In Figure 2-4, we can see the different components and the arrows that show how the data moves around the app.

The Three-Layer Architecture

If you look at the diagram and try to find a repeated pattern, you can conclude that the data flow is going through three main components:
  • It starts in the UI, where tapping on a button generates a data object we can work with. We call it the presentation layer or the UI layer. If you are aware of design patterns like MVVM/VIPER, the whole design pattern is considered part of the UI layer.

  • Afterward, the data object goes to a component that needs to decide what to do with it. We call it the business layer. The business layer doesn’t have to be a singleton – but it holds some logic that can be relevant for the type of UI, device, or platform.

  • At the end of the flow, the data is sent to the server or the local persistent store (in this case, Core Data). It can also connect the data to a third-party library. We call it the service layer or the data layer.

Most of the client apps, not only on iOS but also on mobile, work this way – some action on the UI, a decision, and a service that handles the data. It is called the three-layer architecture , and it is popular among many systems and applications.

Note

Some call this architecture the “N-layer architecture” because, in some cases, you have more than three layers. You can also find it under the name of “three-tier” architecture, but when talking about iOS, this is a mistake – it is common to call “tier” to a layer that is physically separated. In our case, a “layer” is the better term to use.

Let’s take a look at Figure 2-5 in an even more abstract way.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig5_HTML.jpg
Figure 2-5

Three-layer architecture

Organizing our app to three layers has enormous benefits in terms of scalability and maintenance. Let’s name some of them.

Note

The following list is crucial to understand the benefits of using Swift Packages in your projects. Swift Packages take these benefits and just leverage them.

Problems to Mini Problems

We all know that solving a small problem is more comfortable than solving a big one. So, breaking our app (“the problem”) to layers makes it easier to handle the different steps of our data flow.

And this is not just a marketing statement.

You might think it’s mainly relevant to your app’s development stage, but in fact, in the development stage, breaking your app to different layers has a less significant effect.

Debugging, testing, and adding more capabilities are the parts where your app’s separation really shines.

Because the data is moving between distinguishing layers, it’s easier to catch bugs and issues, and because each layer has a different responsibility, it becomes simpler to write tests. Smaller problems are easier to solve.

But it doesn’t have to end in the architecture level – the UI layer, while separated from the business logic and the service layer, can be severed to smaller problems (just like the house example I mentioned earlier). As a matter of fact, separating your app to smaller problems is a recursive process that can help you maintain your code over time. In the UI layer example, you can use all kinds of design patterns to build your UI layer, such as MVC, MVVM, VIPER, and so on. It is another separation, and in this case, it helps you handle the most sophisticated UI designs.

And of course, the design pattern you choose for your UI component can also be split into small files and so on.

Stability
Some inflatable boats are constructed with multiple air chambers. The reason is apparent – if one of the chambers loses its air pressure for some reason, the other chambers make sure the boat doesn’t sink. The modular structure of inflatable boats makes them more stable and safer to use (take a look at Figure 2-6).
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig6_HTML.jpg
Figure 2-6

An inflatable boat with a puncture

Think of your code as an inflatable boat and your bugs as low air pressure (or worse, a puncture).

It’s easier to replace and fix a damaged component in your codebase than trying to fix a broadside bug.

The image of modular apps to an inflatable boat can be applied to other objects from the real world as well, from machines, cars, houses, weapons, and more.

Modular objects narrow the damage to a particular component and can help fix the issue quickly and simply.

Teamwork

We all had to deal with git conflicts in our projects when trying to work as a team. Git conflicts result from two or more developers editing the same code line, and we all know that sometimes it is unavoidable.

But separating your app to different components reduces the number of potential conflicts.

Sure, it has a lot to do with the way your team works, but in the end, most chances your developers will work on different classes or even various modules/libraries and connect only with protocols.

There are cases (that we will cover later) that your codebase can be separated not only to different components but also to different repositories. Take into account that this approach carries some other challenges, such as security and versioning. Both issues will be covered later in this book.

But handling git conflicts is just part of the story. This kind of separation really helps when trying to manage teamwork. Give one developer the task of maintaining a service component, and for other developers, a mission to address the UI can simplify things for the team. These are not just different classes – these are genuinely different worlds that talk with each other through protocols.

Build Time

Build time is a painful topic.

To understand why build time is affected by the architecture you choose, we need to know how incremental build works.

When we make changes in a specific file, other files that depend on the particular files must recompile. The same goes for more files that depended on the recompiled files and so on.

Notice I didn’t mention changes in classes, structs, or enums, but files. That’s because it is entirely valid in Swift to add multiple entities in the same file. In this case, change in one entity requires the recompilation of the whole file.

Now, of course, this is true for any architecture you choose to build your app.

So, what is so special about three-layer architecture? This architecture makes sure the dependencies are configured in a way that fits your work and the scale of your project.

When you make changes in the network layer, you recompile the UI or even the logic that depended on it.

But it’s not the same when working on a top layer like a UI layer.

When your dependencies are laid according to your data flow, it eases your compile process and can help you achieve better build times in the future.

Also, encapsulating some of the layers in a package of their own like we will learn shortly in this book can guide the compiler to optimize its build.

In short, compilers love code separation. Let’s give it to them.

Redesign Our App Architecture

After the meeting with her boss, Emily felt helpless. She understood now that her iOS project is not ready for these kinds of changes.

Yes, the code was organized beautifully in folders, but that was it. Too much logical code was located in the UI, and besides the network layer, there wasn’t any central place that could be shared with other clients. She read some great articles about app architecture, but this is an old project with over 1000 classes. Where does she start?

This is not uncommon – we have an existing, messy project, and we want to reorganize and separate it into UI, business, and services. How do we start?

We need to do a few steps, which are common both for reorganizing our project and building it from scratch.

First, we need to draw our project architecture on a paper or on a whiteboard.

We need to talk about our layers and decide what design patterns we are going to use.

And finally, we need to decide what entities we’re going to have in each layer and the protocols our layers use to communicate.

Take a Paper and Draw a UML

A paper? When are we in the 1980s?

No. But sometimes it’s useful to take good habits from the past. Paper and pen have one fantastic feature – they have barely any constraints when trying to spill your thoughts and draw them. With this method, the path from your brain to a visual thought presentation is concise.

And what you need to do right now is to draw a basic UML of your app essential components as you see them.

Some guides:
  • We always start with the apparent and straightforward components. Afterward, it will be easier to complete the others. Usually, it means starting with one of the sides – services or UI.

  • Before drawing, try to make a list on the side – all UI screens and all the “others” – network, DB, and more.

  • Take into account any third-party libraries you might have.

  • Try to locate any global variables, constants, structs, and functions, and understand what the missing components are.

Figure 2-7 shows a basic UML drawn on a paper.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig7_HTML.jpg
Figure 2-7

A UML drawn on a paper

Do This in Collaboration

In most cases, drawing a basic diagram on a paper or on a whiteboard can collaborate with our teammates. Studies show that different people have a different perspective on how systems are built, and they observe and analyze them differently.

Some people are more “attached” and feel natural with the bottom layers, meaning the services. They tend to be more involved in the implementation details and integration with other libraries.

On the other hand, other people like to take a look at the system from the preceding one. They don’t want to get down to the implementation details but instead talk about data flows and user interaction.

This means that we can add product and QA members to the discussion to ensure we understand the requirements and how the data moves in our app. This process has to be synchronized with all the relevant people.

Relations Between Components

It is essential not only to understand what the components are but also to connect and communicate with each other. At this stage, it is enough to mention the relationship in one sentence – “fetch data,” “save data,” “receive progress,” and so on.

This is the foundation stone for defining our layers’ interfaces when we get down to the details (see Figure 2-8).
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig8_HTML.jpg
Figure 2-8

Components’ relations

It is also a tool to figure out what is the primary goal of each component. If we see a component that does different tasks, we might consider splitting it, to keep the principle of “separation of concerns.”

What Side to Start?

When we try to represent our three-layer system, we always have the question – on what side do we start?

Are we starting on placing all the UI screens and continuing down to the very bottom layers?

Or maybe the opposite – encapsulate all the services to components, build upon them the business logic, and connect the UI?

As I mentioned earlier, there is no clear answer to that. If you have a good understanding of your app’s low-level implementation, it is perfectly normal to start your diagram with the service layer.

On the other side, if you are more attached to the user flows, you can start by drawing the UI layer, and starting from this point, derive the rest of the architecture.

For most people, the combination of the two methods is the winning path.

Moving back and forth between the two sides keeps you in context with the app framework and gives you a better understanding of what business logic you need.

Opened and Closed Layers

We said that our app works in layers – we have the service layer , which serves the business logic layer, which helps the UI layer finish its tasks. We also have third-party libraries that sit under the service layer. In general, our data flow works so that the data has to pass in each layer to get to the bottom of the hierarchy. But we know that reality is much more complicated than that. We definitely have situations that we have to skip a layer.

For example, we might have a business logic component that works directly with a third-party library or a UI presenter that works with the DB layer and skips the business logic component.

This is not ideal, but it sure can happen.

In these situations, it is essential to define if a layer can be “skipped” or do we have to pass it through.

A layer that can be skipped is called an “opened” layer.

If we have to pass it, it is called a “closed” layer.

Why does it matter?

If you remember from earlier, we discussed the principle of “separation of concerns.”

As a reminder, the separation of concerns states that each component has one responsibility, which is responsible only by this component.

For example, if we have a layer that handles the calls to the DB and we want to bypass it and call the DB directly from a business logic layer, we, in fact, violate the principle of “separation of concerns.”

There are times that we have to make compromises. For example, we want to prioritize simplicity over separations. In this case, it is important to define it and be aware of that.

Define Design Patterns

What is the difference between architecture and design patterns ? This is a widespread confusion many developers (even seniors) have.

Remember my “house” example? So sorry, but I’m going to use it again.

Architecture is the skeleton of the house. It’s the decision how many floors it has or if it has a basement or an attic.

The house’s architecture can also define the primary purpose of each floor and how people can move from floor to floor – elevator or stairs.

So, what is the design pattern in this metaphor?

A design pattern is the internal structure of each floor – how many rooms, where are the doors, the furniture, or any general design decisions.

Going back to software design, architecture is how our app is built, its outline. The layers are the “house floors,” and the interfaces between them are the stairs.

A design pattern is how each floor is built.

Think of a design pattern as a tool that can help you solve common problems. For example, to solve UI problems, you can pick one of MVC/MVVM/MVP/VIPER design patterns.

For business logic, you can choose a façade or decorator.

A design pattern is also a solution for communication between layers, for example, choosing between closures, delegates, or notifications. See Figure 2-9.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig9_HTML.jpg
Figure 2-9

Design patterns as part of the planning

While design patterns are internal, it’s important for our planning because it completes the final picture when discussing architecture. It brings everything in context in both ways and laying the ground for better decisions.

Define Your Entities

What are entities? Entities are the way we keep information in memory. Because we want to create a full separation between our components, we also want to define a different entity for each layer to leverage our components’ flexibility.

For example, the data layer can generate a particular struct to hold the information fetched from the data store, and when the business logic reads it, it converts it to a different type, more comfortable to manipulate. The same goes for the UI layer, which probably needs to work with a different kind of information suitable for its needs. Take a look at Figure 2-10.
../images/505703_1_En_2_Chapter/505703_1_En_2_Fig10_HTML.jpg
Figure 2-10

The different components and their entities

Using different entity in different layers has several advantages :
  • First, this way, our components truly decouple from each other. You can even reuse a component in a different project and only need to convert the entity once.

  • We know that each layer has a different goal and different concern. This means that each layer has different needs. Using the same entity structure is not only coupling the layers, but it also makes it hard for them to make use of the entity for their internal implementation. Converting the entity to a more convenient, purpose-specific form can help the layer achieve better performance and efficiency.

Notice you don’t have to really “create” a new structure when a new data is receiving.

You can use protocols and extensions to work with an existing entity, but in a better and more convenient format.

Define Your Interfaces

Last but not least are the interfaces.

Interfaces (or protocols) are essential when designing architectures. They’re one of the main methods to decouple components.

Working with interfaces is our way to test, reuse, and replace components easily.

Defining the interfaces is also important at the planning stage because it’s a great tool to help you verify that your component fills its goal. The component interface has to be aligned with its primary purpose, and by thinking ahead, you might discover that your class has few more responsibilities and has to be split.

Defining interfaces can help you understand your component’s real mission and also your data flow.

Summary

We learned why separating your app into layers is so important in many aspects. But it is also a precondition for starting to implement Swift Packages in your project and to build an excellent foundation for your codebase.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.94.171