When talking about software architecture, there is no silver bullet. However, there are some principles which every system should comply. Some of those principles are clean, reusable, and testable code. Service-oriented architecture (SOA) can provide that for us, and it’s easy to implement.

What is service oriented-architecture?

SOA is a style of software design where services are provided to the other components by application components, through a communication protocol over a network. The basic principles of service-oriented architecture are independent of vendors, products and technologies.

What is a service?

A service is a unit of logic that runs in the network with the following characteristics:

It handles a business processes

It can access another service

It’s relatively independent of the software

It has only one responsibility

You should perceive services as tools. We will import them in other classes, and with them, we are going to build features. This structure will give us the benefits mentioned earlier. These tools will help us handling API, Core data, location, any third party code or business process.

Two types of services

In our implementation of SOA, we use two types of services: managers and regular services. Structurally they are the same, but managers have these attributes:

It is a service built around some framework - Core data

Generic and straightforward - Knows only to fetch, save, update and delete data, doesn’t work with specific data

Easy to replace - If we want to replace Core data with Realm we only need to change a manager

Managers are imported only by regular services and not by low-level objects such as view controllers or view models

Should reuse in another project - Because of these benefits we can easily reuse these tools in other projects

On the other hand, regular services have the following characteristics:

Code is not generic - Fetching particular objects from Core data

They should be imported in low level objects and other regular services

Can’t reuse in other projects - Have specific tasks

SOA implementation

We will implement APIManager and API services. I already wrote about APIManager and if you want to know more details, take a look. That one is a bit different (APIManager in that example is initialized as a singleton), then the one we are going to create, but principles are the same.

This is the structure we want to have. Our network services will import the API manager, and our view models will import network services.

We are going to build our APIManager around AlamoFire. We want our code to be loosely coupled. So if we want to change AlamoFire with other library or our custom network manager, only class we will have to change is the APIManager. Also, as mentioned above, we can reuse this implementation in other projects.

We initialize our APIManager using dependency injection. We will use dependency injection in all our managers in regular services. We have to use it because it makes our code loosely coupled, easier to test, more comfortable to extend and easier to reuse. I won’t go into details about dependency injection, because that is a vast topic if you want to find out more you can read here.

Our APIManager will only know how to fetch and parse data. That is all it needs to do. In real life example, it should upload a photo or a file, but I wanted to make this as simple as possible so we won’t add that code.

As you can see we didn’t import AlamoFire. Now let’s move on to network services. We will implement UserNetworkServices. Again we will use dependency injection when initializing.

UserServices is an object that will store user for us. In the real example it should have access to Core data or another similar framework so it could fetch user before initialization. Again because this is just proof of concept we won’t add Core data into the project.

We will add two public methods for fetching and updating the user.

Now we have an implementation of these API calls in only one place. If the backend team changes something, for example, add a new parameter, we will have to change only this class.

In UserProfileViewModel we are using UserNetworkServices as a tool to complete the feature. Moreover, we are going to reuse that tool in other view models. Again we are using dependency injection to provide services to the class.

In AddPaymentOptionViewModel we use same services. We have reused our code, and updateProfile method is working in both use cases with any parameters we need.

By implementing managers and regular services we now have clean, reusable, testable and loosely coupled code. However, we can still improve our SOA. We can save memory. We should instantiate each service only once. There is no benefit of having multiple instances when we can reuse them. So let’s make them singletons.

Dependency Container

Dependency container is an object that will manage all dependencies in the project. Because it’s lifecycle should be same as the lifecycle of the app we will create it in AppDelegate.

As you can see all dependencies are lazy so we won’t initialize them until they are needed. We are going to use these references when creating our low-level objects. If someone tries to create a new service instance outside of dependency container and create a pull request, we should reject it. If we stick to this rule, we will have benefits of singleton (only one instance during lifecycle), and we can use dependency injection when initializing services.

How to use services from dependency container?

Dependency container is responsible for creating view controllers and view models. It has access to all services, so it will insert them to controllers and view models using dependency injection. We will use the factory pattern for creating them.

First, we will define protocols for instantiating controllers.

Dependency Container will implement those protocols. We will use constructor injection for view models and use property injection for services (constructor injection and property injection are dependency injection patterns).

Dependency container will also handle coordinators. For navigation, we will use flow coordination pattern. I won’t discuss it because I’ve already written about it. Here you can find how to make custom transitions using coordinator and how to how to implement delegation pattern using MVVM and flow coordinators.

We are done! Here is the project example. I hope you’ll make a good use out of it.

Conclusion

SOA relies on services - handle the business process, independent, can access another service and dependency container - instantiate all services and provides them to low-level objects. We used services as tools for building features and container for providing those tools to our classes. SOA makes our code clean, reusable, testable and loosely coupled. I highly recommend you to try it out.

If you liked this article and you want to hear more about similar topics, please clap, share, subscribe or comment. Thanks!

Resources