Application of functional programming in front-end authority management

What problem to solve

This article is mainly their own in the actual business development of some summary, write out the hope to discuss with you.

First, a brief introduction to the business background:

  • What we have developed is a 2B enterprise training SaaS system. Enterprises can train their employees on the platform in the way of live broadcast.
  • This SaaS system can be connected to different platforms, such as Dingding and wechat, etc. (different platforms will limit some functions, such as Dingding cannot display employees’ mobile phone numbers), and can also be deployed on the internal network (the internal network will close some online functions). Due to different deployment environments and interconnection systems, the functions available on platforms are limited, and the corresponding front-end permissions are different.
  • The front-end development here mainly involves two parts, account system-level training management and control of live broadcasting in a single room, which are in a SPA and require a set of systems to manage the rights of the two parts.
  • Training management will be divided into account administrator, sub-administrator (system management role may be added later), and live broadcast controller will be divided into lecturers, guests, assistants and other roles. All the people here might be on the same control page, but the UI will be different depending on the role.

In summary, on different deployment platforms, people of different levels (roles) in the same room will see different interfaces and use different functions. Also, a role is limited by the deployment platform and the platform services purchased by the master administrator, or the interface may be different as the master administrator turns off/enables certain functions.

Therefore, we need to build a set of rights management system to comprehensively process these information (platform, account, role) and ensure that each role can see different interfaces. You can see that the permissions we are talking about here are not limited to simple role permissions, but also include the platform and account administrator restrictions above the role.

Because the final permission depends on the account logged in, we put the permission and account information together in the development, collectively known as metaConfig, namely account meta-information, including the basic information of account name, role, main account, specific role information, role permissions, etc., which will determine the final interface display.

How to solve

We use React and Redux for development. MetaConfig objects can be managed directly in Redux.

  • In the view component, the connect function can map the property or permission data configured in metaConfig to the view data required by each component, and finally present different interfaces.
  • Within the routing controller, the metaConfig can also be retrieved from Redux to determine routing permissions.
  • The permissions of some request methods can also be determined according to the corresponding metaConfig attribute.

In our system, metaConfig processing is placed in reducer, there is a default defaultMetaConfig, and the final metaConfig is generated by reducer. The key to permission management is how to generate metaConfig corresponding to each role, which can be summarized as follows:

metaConfig=f(defaultMetaConfig)

Layering (piping)

Breaking down complex problems into simple ones is an important tool in development. Here we can divide the permission management into multiple levels by hierarchical processing, and each level corresponds to a processing module. Such a large permission processing process becomes one that addresses each level of permission processing.

Let’s take a look at the factors that affect system permission management: deployment mode (external network and internal network), docking platform, services purchased by the account administrator and functions enabled/disabled, account-level roles (account administrator and sub-administrator), and room roles (administrator, lecturer, assistant, guest, etc.).

We abstract each layer into a processor called Parser, which can be divided briefly into: deployParser (deployment mode), platformParser(platform), accountParser(account), and roleParser(Role).

In UNIX, there is the concept of a pipeline, where the output of one program becomes the input of the next. In conjunction with our business, we can input a default metaConfig and then output a metaConfig through successive parser functions.

The simple implementation of pipeline in JS is as follows, the processing order of compose function is reversed, you can check the implementation of compose in Redux.

// pipe function
const pipe = (. funcs) = > {
    if (funcs.length === 0) {
        return arg= > arg
    }

    if (funcs.length === 1) {
        return funcs[0]}return funcs.reduce((a, b) = >(... args) => b(a(... args))) }Copy the code

Throw our abstract parser into the pipe function and the result is as follows:

metaConfig = { ... pipe( deployParser, platformParser, accountParser.createAccountParser(account), roleParser, )(defaultMetaConfig) }Copy the code

Note accountParser createAccountParser (account) this line, we analysis the following section.

This allows us to pipe the handling of permissions into multiple levels, each of which operates on the corresponding properties in metaConfig. Because it is a functional process, we can directly calculate the metaConfig in the Reducer and save it to Redux.

There are two cases of handling permissions (not only permissions, but also some basic account information) :

  • Direct assignment, such as account information.
// Data to reset newconfig. isSuperManager =false
        newConfig.isAdmin = true
        newConfig.name = manager.name
Copy the code
  • The merge operation, merge the permissions at the current level with those transferred from the previous level, and then transfer the permissions to the next level. Because there is a concept of restricted permissions, if platformParser doesn’t have SMS, then accountParser probably doesn’t either. Merge this level with the upper level using & in the merge function, and pass the resulting permissions to the next level.
accountParser = metaConfig= > ({
    ...metaConfig,
    Merge accountParser to merge metaConfig from a higher level. There may be multiple permissions to merge
	somePermission: mergeSomePermission(metaConfig.somePermission),
	...
})

// Merge to perform the & operation,
mergeSomePermission = prePermission= > {
	// The current level does not have permission to use SMS
    prePermission.canUseMsg = prePermission & false.// Each merge function can handle multiple permission points. }Copy the code

Refinement stratification (Corrification and composition)

The above layers can be used to solve the rights problem in a large way. However, the rights in services are dynamic and constantly expanding. How to deal with these problems in service iterations? For example, mergeSomePermission is limited to false in the example above, but it is possible that some roles have this permission while others do not. A simple Parser on the Account level cannot handle differences between different accounts, and different levels of accounts may have different permissions. The same level also requires different handler functions that use Account as a parameter to refine each processor.

We might need the following code to add handlers for different accounts to the account permission processing:


accountParser = (account, metaConfig) = > {
	cosnt { superManager = null, normalManager = null } = account

    // Handle superManager and normalManager permissions separately
    let newConfig = superManagerParser(superManager, metaConfig)
    newConfig = normalManagerParser(normalManager, newConfig)
    
    return newConfig
}

superManagerParser = (superManager = null , metaConfig) = > 
// If it is the master administrator, process it
superManager
? ({
    ...metaConfig,
    // Process according to superManager information
    somePermission: mergeSomePermission(superManager, metaConfig.somePermission),

    // The master administrator function needs to handle more permissions
    someSystemPermission: mergeSomeSystemPermission(superManager, metaConfig.somePermission)
	
})
: metaConfig

normalManagerParser = (normalManager, metaConfig) = >
normalManager
? ({
    ...metaConfig,
    // Process according to normalManager information
	somePermission: mergeSomePermission(normalManager, metaConfig.somePermission)
})
: metaConfig
Copy the code

Having seen some hints of functional programming in the previous pipeline processing, we can continue to work with the above functions using some functional methods. AccountParser in pipeline processing. CreateAccountParser (account) is to deal with this problem.

// The function is currified
createSuperManagerParser = (superManager = null) = > metaConfig => 
// If it is the master administrator, process it
superManager
? ({
    ...metaConfig,
    // The master administrator function needs to handle more permissions
    someSystemPermission: mergeSomeSystemPermission(superManager, metaCofig.somePermission)
	somePermission: mergeSomePermission(superManager, metaCofig.somePermission)
})
: metaConfig

// The function is currified
createNormalManagerParser = (normalManager = null) = > metaConfig =>
normalManager
? ({
    ...metaConfig,
	somePermission: mergeSomePermission(normalManager, metaCofig.somePermission)
})
: metaConfig


// Merge into an account-level parser
const createAccountParser = account= > {
    const { normalManger = null, super_manager = null } = account || {}

    return pipe(
        createSuperManagerParser(super_manager),
        createNormalManagerParser(normalManger),
    )
}

Copy the code

After processing the two Parser functions using currization, we can make them both accept metaCofig as an argument and continue to use a pipe to assemble an account-level accountParser with metaConfig as an argument. We then use currification and composition on the Account layer so that the Parser can be piped again.

The same can be done with the character processor roleParser. With RBAC permission management, one role corresponds to one Parser, and a large roleParser is synthesized using Corrification and PIPE.

At this point, the application of functional programming in front-end rights management is enough.

Why do you do that?

There are basically the following reasons:

  • Layered decoupling. Separate the parts of the code so that each level only deals with its own part. The code is clear and easy to maintain, and the rest of the team can quickly understand the ideas.
  • Combinable extension. Currization and pipelining and composition allow for unlimited gradation, and even if permissions become more complex later on, you can handle it by adding layers and combining Parsers.
  • The whole process is functional, only simple input and output, no impact on the external system, put in the Reducer of Redux really sweet.

conclusion

This paper mainly introduces the application of functional programming (pipeline, Currization, composition) in the front-end authority management, which disassembles the complex authority management into fine-grained parser functions through hierarchical decoupling and multi-level layering. The level is limited, in fact, it is not very deep, but basically solved the existing problems. Business development for a long time, may feel that there is no promotion, but in the daily development can also live to learn and use, some basic ideas of programming actively applied to the development may have unintended results. Write here for your reference, if you have better ideas are also welcome to discuss.

Original address: github.com/woxixiulayi…