Coding Required

The Ability System

2019-08-173 min read

While I was developing YoPrint Single Page Application, I ran into an interesting problem. I needed to show the user differentiated UI based on the features available for the subscription, the user’s permission as well as the users preference.

Some of the challenges are that in the future, a feature might be split into many sub-features for better customer segmentation. Broader permission may need to become more granular.

Therefore, I wanted to design a decoupled system that’s easy to manage and adapts well for future change. I came up with the Ability System.

What is an Ability

At the core, features, permissions, and user preferences dictates what user gets to see on their screen. Our primary role on the frontend is to show the users what they are allowed or configured to see. Enforcing these constraints is the role of backend.

Therefore, an ability is simply the user’s ability to view a UI element. The UI element could be a button, dialog, or a page. To put it in another way, “can the user view this?”

Naming an ability

I recommend an ability name to only start with canView. For example, if the user has update_sales_order permission, then the user has the following abilities:

I also advise against negative naming for ability. You shouldn’t use canHideViewSalesOrderEditPage or hideViewSalesOrderEditPage.

The first benefit of using canView prefix exclusively, is that it makes the code easier to read. For example, in Angular, you can do this.

<button mat-button (click)="onEditButtonClicked()" *ngIf="abilities.canViewSalesOrderEditDialog">Edit</button>

The second benefit is uniformity. By having uniform ability names, it lessens the cognitive overload, making it easier to reason about as well as make fewer mistakes.

Abilities are either present or absent

I also recommend an ability to be of boolean nature and not enum based.

Option 1 (Recommended): You can use an object whose keys are the ability names, and the values are booleans. For example:

const ability = {
    canViewSalesOrderEditPage = true;
    canViewSalesOrderEditDialog = true;
    canViewSalesOrderEditButton = true;
    canViewSalesOrderRemoveButton = false;
}

Option 2: You can use a string array where the presence of an ability indicates true

const ability = [
    "canViewSalesOrderEditPage",
    "canViewSalesOrderEditDialog",
    "canViewSalesOrderEditButton",
]

Centralize Ability management

In Angular, you can create an AbilityService that can listen to plan details, user permissions, and user preferences, and translate them into abilities. Using RxJS, you can broadcast every time an ability changes. You can achieve something similar using NgRX / Redux as well.

Having a single place to configure all your abilities makes changes in the future a breeze.

How to translate features, permissions, and preferences into Abilities.

The best approach that worked for me is to start with an empty ability object. By default, the user cannot see anything.

First, add all the default abilities a user may have like canViewDashboardPage, and canViewProfilePage.

Next, add all the abilities made available through the current user’s subscription/plan. For example, this might be canViewProductionControlPage, and canViewInventoryListPage.

Now we have to apply user permissions. Permissions, it is best to remove abilities if the user doesn’t have the correct permission for it. For example, if the user doesn’t have view_customer permission, then remove canViewCustomerListPage and canViewCustomerDetailPage.

Finally, apply user preference. For example, if the user prefers to use the grid view for the customer list page, then you will add canViewCustomerListAsGrid.

Closing Thoughts

I believe the Ability system is a good, extensible way of managing features, permissions and user preferences. It’s even possible to extend the system to support feature flighting. Let me know your thoughts on it.

Anbin Muniandy
CEO & Principal Engineer, YoPrint