Hi SharePoint Devs,
In this post, I would like to tackle a notion very important to me as a code lover: the Separation of Concerns (SoC) and how we can implement this principle in a SharePoint Framework solution. For the example, we will re-use the Kanban SPFx WebPart I presented in this post.
If you want to follow the steps of this article and do the changes by yourself, you can download or clone the github repo for this webpart here
What is SoC ?
SoC is a principle where the code is splitted into units each responsible for its own business (UI, Business, Data Access, …), the units (or layers) encapsulate their internal business data and produce a result according to the input we pass to them. The units don’t care about what’s internal in other units, they only care about the result they get when they use the features of other units.
Each unit is easier to maintain, to unit-test, and even entirely rewritten if needed. An application is then a set of units that communicate with each other. We remove all intricate dependencies we have to deal with when the concerns are not really separated (calls to data API directly in the UI code, changes to do in the UI whenever the data schema changes, …)
Moreover, if you build your classes and services generic enough, you will be able to take it as they are to reuse in a totally different context.
In practice, it means we need to design and implement our units of code in a way that they will never depend on a actual implementation of any unit, because it means it would be very difficult to maintain or change in the future. Instead, units will only be aware of interfaces they have to deal with, they should never have a clue of what’s going on internally in the units they consume. It is, actually, one of the pillars of OOP.
From this point, I will talk about these units as Services. A service is nothing more than a unit of code dedicated to a specific concern, it is generally described by a public interface.
So, to achieve that, in practice, we have to define public interfaces that will be known by any consumer of the services
A ConfigurationService class
We want to remove all dependencies to the WebPart in our services. However, the WebPart class handles something very important: the configuration via the Property Pane. Under a src/services/ folder, let’s create a class that will hold the configuration information. Let’s also create its public interface:
You don’t have to worry about the ServiceKey things for now.
We have now to relay the configuration information from the property pane to this service class. In the KanbanBoardWebPart class, add the following changes
A DataService class
In order to clearly separate the concerns, we have to move all the methods responsible for the data access from the WebPart UI class to a dedicated service class. In the src/services/ folder let’s add a DataService.ts file. We then move all the data related methods from the UI to that service. We also declare the public interface of the service.
Again that ServiceKey and now ServiceScope, what are they ?
These classes are the implementation in SPFx that allow the developer to leverage dependency injection. Instead of passing a reference to each single dependency, we pass the scope as argument to the unit and the unit calls a consume() method to get the service it needs.
With the line
export const DataServiceKey = ServiceKey.create<IDataService>(“kanban:data-service“, SharePointDataService);
We declare the ServiceKey, the key that will identify the service within the scope
Now that we implemented all the needed logic in the Service implementation, we have to adapt the WebPart class code to remove all the data accessing logic. We also need to consume the services, let’s adapt the KanbanBoardWebPart class as follows :
You can see now that the code of the WebPart class only contains things related to the UI. All the data fetching and updating is encapsulated within the service.
Cool ! My concerns are separated, I can maintain it in a better way now !
Exactly, if anything in the SharePoint data schema changes, you only have to adapt the DataService implementation, the UI will not change at all !
But there is even better: Implementation dedicated to current environment.
Let’s take the case of Local Workbench or unit testing and the needed mock data. We create a mock implementation for the Data Service, it uses mock in-memory data, however it implements the exact same interface as the real service, that way, the UI layer will never even know whether it is in local workbench or in a real SharePoint page
We have now to tell the WebPart to use this implementation when it used in the local workbench or in unit testing.
You might have noticed, in the onInit() method of the KanbanBoardWebPart class, I removed the SPFx PnP setup and I added some AppStartup.configure() call. What is that ?
A great advantage of the SPFx ServiceScope class is that it holds the default implementation of the services. However, you are able to create child scopes that will contain specific implementation for some services, all the services that have no implementation at the child scope level will take it from its parent scope all the way up until the root ServiceScope.
I have a AppStartup class that make all the required configuration and instantiate the appropriate services according to the context, all the consumers of the services will not know which implementation they use, they will just use it !
In my solution, the AppStartup class is the only one that knows about the current environment (Local, Test, SharePoint, ClassicSharePoint). I designed the class to be responsible of returning the appropriate scope of implementations according to the context. Take a look on the configureForLocalOrTestContext() method to check how to register specific implementation of a service for a given key.
the configure() method that is called by the KanbanBoardWebPart class performs all the required startup configuration and returns the ServiceScope that will be used by all the layers of the application.
With this implementation, all concerns are separated and are easier to maintain, fix, improve or replace ! The only item you have to pass to the units is the ServiceScope.
I encourage you to read the following articles about the SPFx Service Scope:
You can see the whole solution on the V2 branch on my github repo
Hope you liked this post and it will be helpful.