Cross-desktop componentization practice

Cross-desktop componentization practice

Background

Windows Qianniu has a lot of functions. When will Mac Qianniu be able to align their capabilities?

I believe that all cross-platform applications have encountered such a dilemma. Due to the complexity of platform differences, the cost of maintaining multi-terminal products is very high, and there are often problems with inconsistent multi-terminal experiences. This is the case, and our team maintains two cross-terminal products, PC Qianniu and PC Wangwang. Under the dual pressure of performance and experience, it is imperative to build a unified multi-terminal PC application cross-platform development framework.

This article mainly introduces our thoughts on the componentization part, solution selection, some problems encountered and solutions in the Qianniu PC cross-terminal framework.

The so-called framework is both a "frame" with certain constraints and a "frame" with certain support. In the IT context, the framework specifically refers to a support structure with certain constraints designed to solve an open problem. More components can be expanded and inserted on this structure according to specific problems, so as to build a complete solution to the problem more quickly and conveniently.

Why do we need componentization?

Why do cross-end frameworks choose to be componentized?

The framework itself generally cannot directly solve a specific problem, but it provides some basic capabilities for connecting and combining related components to solve the problem.

The scientific nature and ease of use of the framework directly determine R&D efficiency and product quality.

Componentization is a very suitable technical solution to solve the expansion and reuse of framework functions.

An application framework designed using the componentization model generally has the following characteristics:

  1. Excellent scalability
  2. Excellent reusability
  3. High flexibility, easy to assemble or offline
  4. Modify the function, the impact is very small
  5. Very suitable for team collaboration

Each of these advantages is what we dream of, so componentization is almost an inevitable choice for us.

What is componentization?

Componentization refers to the process of splitting and reorganizing multiple functional modules when decoupling complex systems, with various attributes and states reflecting their internal characteristics.

For example, you want to build a car, but find that the car is too complicated and difficult to realize, so:

  1. You break down the car into modules such as chassis, engine, gearbox, wheels, etc., and define their respective responsibilities.
  2. Then you ask your friends for help, agree on the component standards first, and then let everyone implement one of the independent modules.
  3. You also need a control system that allows these modules to cooperate and work together.
  4. Since everyone develops according to a standard, these modules can be easily assembled together, managed and controlled.
  5. So, you have realized a car.

A functional module here is a component, and the system used to control the collaborative operation of components is the component framework.

Several issues that the component framework needs to solve:

  1. How to discover components
  2. How to manage component lifecycle
  3. How to call between components
  4. Public basic capabilities provided

How to choose a componentized solution?

There are many component-based implementation solutions. How do we choose the technical solution that suits us?

There are many componentization solutions in the industry, such as com components under Windows, ARouter components under Android, ths components based on message bus, prg::com components developed by Qianniu, and some components based on RPC framework in a broader sense (microservices).

In my opinion, there is no best componentized solution, only a relatively suitable one. According to the business scenario, you can choose a solution that meets the current business needs, takes into account the future development needs, and is easy to use and maintain.

Here are some dimensions for reference when selecting component solutions:

  1. Discovery Mechanism
  2. Communication Mechanism
  3. Cross-platform
  4. Across programming languages
  5. Maintenance costs
  6. R&D efficiency
  7. Compile Dependencies
  8. performance
  9. stability

In the cross-end Qianniu scenario, our priority is:

  1. First of all, it must support cross-platform.
  2. The second is good maintainability. In the long run, maintainability has a profound impact on product quality, performance and R&D experience.
  3. Then there is good performance and stability,
  4. Finally, there is better R&D efficiency and R&D experience.

Here we mainly compare the ths component and the prg::com component solution:

  1. THS component: THS is similar to an RPC call framework. All calls are transmitted on the bus in the form of messages. It has runtime isolation and central node aspects, but its interface maintainability is poor and problems cannot be found at compile time. THS is more suitable for cross-team scenarios or open scenarios.
  2. prg::com component: prg::com component is similar to Microsoft's com component, but it supports cross-platform and optimizes the calling method of com interface, making it easy to call. Its interface is easy to maintain, and interface compatibility issues can be found during compilation. Its performance is also very good, which is very suitable for component scenarios within the team.

Finally, we chose our self-developed prg::com as the technical solution for cross-end framework componentization. The following is a detailed introduction to this solution.

Cross-end componentization practice

The componentized solution includes two parts: framework capabilities and component constraints.

Whether the framework design is scientific and whether it is easy to develop, use, and maintain under component constraints are the core factors to be considered in component-based solutions.

  1. The component framework provides the basic capabilities for component operation, including: component discovery mechanism, component life management, inter-component communication, and other common basic capabilities.
  2. Component constraints define the standards that need to be followed when developing components. Their main purposes include: supporting components to run on the prg framework, for example, components all inherit from the prg::com object and need to complete I interface registration. Supporting components across platforms, for example, UI components need to comply with the mvp layering, and when replacing the UI rendering layer, ensure that the business logic is consistent across multiple terminals. In order to facilitate team collaboration, such as file structure, code layering, naming rules, etc., use the same paradigm to develop and use components. ……

Component constraints are defined based on factors such as component type and specific usage scenarios, and the standards for different components are not exactly the same.

For example, the standards for UI components and non-UI components are very different.

The following is an introduction to the prg framework and the component constraints we define in various scenarios.

▐ prg framework

  • Component discovery mechanism

The prg framework uses template technology to scan dlls to generate configurations during packaging and statically register components when loading dlls, thus implementing a component discovery mechanism.

The component discovery mechanism of the prg framework relies on id registration. The components only expose class ids and I interfaces to the outside world, thus achieving complete de-dependency between components.

Let's take a look at the schematic code first:

 // Define a prg::com component
class IxxxService;
DEFINE_IID ( IxxxService , "{4E6A382D-1FDA-49C6-8521-E284DA7B71CC}" )
DEFINE_CLSID ( xxxService , "{D1A52645-7587-4885-ABFD-323BA62905F5}" )

// Create this prg::com component
scoped_refptr < IxxxService > spInterface ;
prg :: PrgCOMCreateInstance ( c_uuidof ( xxxService ), spInterface );

///////////////////////////////////////////////////////////////////////////////
// implement (not exposed to the outside world)
class CxxxService
: public prg :: CPrgCOMRootObject < prg :: CCOMThreadSafeRefPolicy >
, public IxxxService
{
public :
DECLARE_PRGCOM_RUNTIME ( CxxxService , c_uuidof ( xxxService ), "xxxService" , "xxxService" , prg :: GetDependsCLSID ())

BEGIN_PRGCOM_MAP ( CxxxService )
PRGCOM_INTERFACE_ENTRY ( IxxxService )
END_PRGCOM_MAP ()
} ;

IMPLEMENT_PRGCOM_RUNTIME ( CxxxService );

Its implementation principle is:

  1. When packaging, scan all dlls in the directory, traverse and call the GetPrgCOMFactory interface to generate component configuration xml.
  2. When creating an object, find and load the corresponding dll through xml configuration.
  3. When the dll is loaded, the prg::CPrgCOMObjectRuntime<T> static variable g_prgRuntime is created, and the mapping relationship from clsid to this is registered with PrgCOMFactory during construction.
  4. According to clsid, find the corresponding g_prgRuntime variable in PrgCOMFactory and call the CreateInstance static method. Since the g_prgRuntime variable carries T type information, the corresponding T object can be created.

C++ template technology and static registration technology are used here to cleverly decouple components and solve dependency problems and cross-module calling problems.

  • Component Lifecycle Management

The prg component supports seamless cross-module creation, use, and release of objects, truly achieving one-time development and use everywhere, ready to use out of the box.

The prg component uses scoped_refptr reference counting to manage memory, and users do not need to manage memory themselves.

The prg framework supports the creation, use, and release of objects across dll/dylib. For users, dll/dylib is completely invisible. You only need to specify the object type, interface type, and instance name to be created, and then you can start using this interface directly, which is very smooth.

 class IxxxService : public prg :: IPrgCOMRefCounted
{
public :
base :: event < void () > onDataChanged ;
public :
virtual bool GetData ( const std :: string & data ) = 0 ;
}

// Create a new instance of the prg::com component
scoped_refptr < IxxxService > spInterface ;
prg :: PrgCOMCreateInstance ( c_uuidof ( xxxService ), spInterface );

// Get the prg::com component (create if not available, a reference will be saved inside the prg framework)
scoped_refptr < IxxxService > spInterface ;
prg :: PrgCOMGetInstance ( c_uuidof ( xxxService ), instanceName , spInterface );

// Check if the prg::com component instance exists
prg :: PrgCOMHasInstance ( c_uuidof ( xxxService ), instanceName , bhave );

// Delete the prg::com component instance
prg :: PrgCOMDropInstance ( c_uuidof ( xxxService ), instanceName );

We usually encapsulate component acquisition into an interface like the following. For users, calling the interface is as convenient as calling their own code.

 /// Component header file
inline scoped_refptr < IxxxService > GetIxxxService ()
{
scoped_refptr < IxxxService > spInterface ;
prg :: PrgCOMGetInstance ( c_uuidof ( UIAppGuideWidget ), "" , spInterface );
return spInterface ;
}

//////////////////////////////////////////////////////////////////////////////
// Other components directly call the interface
std :: string data ;
GetIxxxService () - > GetData ( data );
  • Inter-component communication

Interface calling and event subscription of prg component.

Interface call

The interface call of prg component is similar to that of com component, the difference is that prg::com has a better encapsulation, which can directly get the I interface object for use. (Of course, it still supports the use of QueryInterface, and different types of I interfaces can be obtained through QueryInterface)

 class IxxxService : public prg :: IPrgCOMRefCounted
{
public :
base :: event < void () > onDataChanged ;
public :
virtual bool GetData ( const std :: string & data ) = 0 ;
}

// Get the component
scoped_refptr < IxxxService > spInterface ;
prg :: PrgCOMGetInstance ( c_uuidof ( xxxService ), instanceName , spInterface );
//Call component method
spInterface - > GetData ( callback );

Event Subscription

Event subscription and dispatch, which is implemented by base::event, is a typical observer mode. When an event is triggered, the observer's base::callback is called one by one in the order of registration, which can easily complete complex process series connection. The event here is at the instance level, and with the account isolation capability of prg, it can solve the event dispatch problem of multi-account business. However, base::event does not currently support registration and dispatch by priority.

 class IxxxService : public prg :: IPrgCOMRefCounted
{
public :
base :: event < void () > onDataChanged ;
public :
virtual bool GetData ( const std :: string & data ) = 0 ;
}
// Get the component
scoped_refptr < IxxxService > spInterface ;
prg :: PrgCOMGetInstance ( c_uuidof ( xxxService ), instanceName , spInterface );

// Subscribe to component events
CBaseEventHelper :: RegisterEvent ( spInterface - > onDataChanged , callback );

// Unsubscribe from component events
CBaseEventHelper :: UnRegisterEvent ( spInterface - > onDataChanged );

▐ Component constraints of the prg framework

What constraints must prg::com components follow?

Different types of components have different standards. To talk about component standards, we must first classify the components.

Taking the Aliwangwang application as an example, the components included in the cross-terminal Wangwang can be roughly divided into the following categories:

Framework layer:

  1. Ali series PC application basic components
  2. Platform-related basic components

The framework and basic components are the base of Alibaba's PC applications. These components are built into the prg framework, thus enabling the ability to quickly build cross-terminal PC applications.

Application layer:

  1. Wangwang Business-Non-UI Group
  2. Wangwang Business-UI Components

Application layer components are mainly used to implement business functions. These components often need to be expanded and modified, and are what we should focus on.

Application layer components can be divided into UI-related and UI-independent components according to their technical implementation. UI components are relatively more complex.

(ps: The UI components use pv layering. The p layer is responsible for controlling the interface logic and is implemented in pure C++. The view layer is only responsible for drawing and input operations. This maximizes code reuse, improves efficiency, and ensures consistency between the two ends of the business. Our UI components all comply with this standard. We chose Qt as the cross-end UI framework. We found that Qt cannot achieve full cross-end UI functions. Considering the possibility of replacing the UI framework or adapting to new platforms in the future, we limit the use of Qt to the UI rendering part, that is, the view layer.)

prg component general standard

prg::com component base standard, all prg components comply with it.

Each prg component is named...Service, exposed to the outside world in the form of I...Service interface, and implemented in C...Service.

Service concept: Service is a prgcom component, an independent business unit within the client, and an abstraction of independent business capabilities.

Interface: IxxxService (in the biz/interface directory, IxxxService.h file)

Implementation: CxxxService (in the biz/xxx/service directory)

Get instance: GetxxxService()

Directions:

  1. All prg components provide services to the outside world in a unified way.
  2. Users can obtain the prg component instance through the GetxxxService() interface, and then use the component through the interfaces and events provided by IxxxService.
  3. The CxxxService is implemented internally in the component and is not exposed externally.

Non-UI component standards

Components that do not include UI interfaces are less affected by platform differences. They are designed internally according to business needs and comply with the basic standards of PRG components.

  • UI component standards

The standards for cross-end UI components mainly include MVP layering, UI lifecycle management, and multi-UI combinations in various scenarios.

The UI component still complies with the general standards of the PRG component and supports all the features of the PRG component.

Service: It is a prgcom component object. When using UI components externally, you can directly operate the service, just like using non-UI components .

UI: It is the overall interface. UI includes presenter and view. UI and view should be clearly distinguished here.

Presenter: It is the logical object of the interface. The p layer controls all business logic and also controls the input and output of the view .

View: It is the rendering object of the interface and is only responsible for interface rendering and user operation input .

In the prg framework, component A calls the UI interface of component B:

Complexity of UI components:

  1. The UI mechanisms, interface styles and operating habits on different platforms are different. How to ensure consistency in business logic on both ends?
  2. The life cycle of UI objects is generally managed within the UI framework. How can we ensure that there are no problems with the life cycle management of UI components?
  3. What if a component contains multiple UIs? How to handle the parallel relationship between multiple UIs? How to handle the nested relationship?
  4. There are too many scenarios for UI components, and the standard definition alone is very complicated. How can we implement them in actual projects?

MVP hierarchical structure

In order to better maintain and reuse UI components and meet cross-end demands, our UI components are developed using the MVP model.

In addition to complying with prgcom standards, UI components must also comply with additional constraints:

  1. Each UI interface is divided into two layers: p and v. The p layer is responsible for logic control, and the v layer is responsible for input and output.
  2. Each UI interface opens an IxxxUI interface. IxxxUI represents the entire UI interface and contains only one GetPresenter() method.
  3. The p layer opens an IxxxPresenter interface, and external methods provided by IxxxPresenter are called to operate the UI interface.
  4. The p layer defines the input and output interface IxxxUIDelegate that the v layer needs to implement. This interface is implemented by the v layer proxy and is only visible in the p layer.
  5. The v layer is only responsible for implementing rendering and inputting user operations.

The main benefits of this design are:

  1. All logic is defined and controlled by p, and the p layer is implemented in c++, which can achieve unification across multiple terminals, Mac and Windows.
  2. The view is contained inside the system and only implements the input and output interfaces, which is very lightweight. Objects related to the view (such as QT objects) will not be spread.
  3. Replacing the view is simple, just reimplement the UIDelegate interface.
  4. You can do unit testing in the p layer by implementing a mock of the UIDelegate interface.

Lifecycle Management

The life cycle of UI components is more complicated than that of general components because the life cycle of some objects of UI components is managed by the UI framework.

Buy Wang cross-end framework, the ui selection is Qt framework, the ui object life cycle is managed by the Qt kernel, and the component life cycle is managed by the prg kernel.

After the UI object is created, the component saves the pointer to the UI object in order to interact with the UI object, but the life cycle of the UI object is managed internally by Qt.

Therefore, we need to establish a mechanism so that when Qt destroys the UI object, our component will be notified that the UI object has been destroyed.

Create and call a process:

Destruction process:

Processing of various UI interfaces

Single UI interface

Multiple flat UI interfaces

Parent-child UI interface

Interface calls under multi-layer UI interface nesting

Since there are too many nested levels, each level needs to open an interface for transmission, which will bring a lot of workload, so a generalized interface transmission solution is provided.

You can refer to the implementation of the IAliwangwangChatBase.h case to complete the interface transfer.

Automatically generate component code

Cross-platform, standardization, and low coupling often mean more complicated coding and difficulty in implementation.

For example, if I want to operate component B in component A and display a dialog box, the code I need to write is:

  1. IxxBService component interface
  2. CxxBService component implementation
  3. IxxBPresenter P layer interface
  4. CxxBPresenter p-layer implementation
  5. IxxBPresenter::IxxBViewDelegate ViewDelegate interface defined by the p layer
  6. CxxBView view layer implementation

Writing code in 6 objects is simply a disaster!!! But considering long-term maintainability and cross-platform, it must be done this way.

Therefore, we developed a code generation tool that can automatically generate component code from templates based on the UI relationships in the above situations.

Due to space limitations, this will not be discussed in detail here.

▐The effect

This cross-end componentized solution has been implemented in the cross-end Qianniu/cross-end WantWant products, and currently two products and three ends have been released and launched.

(Currently, the function of win Qianniu is greater than cross-terminal Qianniu. The win version of cross-terminal Qianniu has not yet been released, so stay tuned)

  1. Completely consistent user experience on both ends, completely reusing the same business logic code.
  2. The cost of dual-end development is naturally reduced by half.
  3. It is suitable for team collaboration, component splitting, and high efficiency of collaborative development.
  4. Components are completely decoupled, which greatly enhances maintainability. Develop once and use everywhere, simple and convenient.
  5. Available tools can automatically generate component code, so you only need to focus on business logic, with high efficiency and consistent style.
  6. It integrates a large number of the group's basic capabilities, precipitates a componentized framework for cross-terminal PC applications, and provides the ability to quickly build Alibaba PC applications.

Let’s go back to our goal when we chose this solution.

  1. The first is cross-platform support.
  2. The second is maintainability and scalability. In the long run, maintainability has a profound impact on product quality, performance and R&D experience.
  3. Then there is good performance and stability,
  4. Finally, there is better R&D efficiency and R&D experience.

At present, the prg component framework performs well in the first three points. In terms of the fourth point, the R&D experience, due to the stringent requirements across multiple terminals, the UI components have many layers and the development is a bit cumbersome. We have alleviated this problem through self-developed component code generation tools.

In general, the prg component cross-end framework can well support the business of Qianniu/Wangwang and even other Alibaba PC applications in the next 3-5 years.

Thinking about the evolution of cross-end component framework

▐Improvement of technical basic capabilities

Framework basic capabilities, such as supporting event subscription priority, supporting component link/performance monitoring, and adding basic capability components.

UI component unit testing capability. UI components are all pv structures, and UI logic is all in the p layer. You can use a simple uidelegate to connect UI logic in series to implement UI unit testing.

Improve R&D efficiency and experience, improve code templates and automation tools, realize automatic generation/completion of interface-level code, and further improve R&D efficiency and experience.

▐Possible business attempts

Towards business componentization

Componentization is not only a technical concept, but also a business concept. Reuse brings low cost and consistency, while decoupling brings business flexibility.

Flexible reuse of components in technology can bring about flexible business combinations and rapid trials.

For example, the IM capability of Wangwang can be an independent Ali Wangwang product or integrated into Qianniu. Components are building blocks. If we think more from a business perspective and provide more and better building blocks, the business can quickly build a new building.

  • Share and build PC component library

Componentization is not limited to the team. The PC business component library shared and built by everyone can maximize the value of componentization.

I hope the PC business will get better and better!

About the Team

We are the cross-terminal technology team of the industry and merchant technology department of Taobao Technology. We are responsible for creating the most efficient one-stop workbench Qianniu for tens of millions of merchants, and providing stable and efficient end-to-end message IM services for hundreds of millions of merchants and consumers on Taobao. Technically, we are deeply engaged in C++ cross-terminal and PC desktop technology (Windows & Mac), providing merchants and consumers with stable, reliable and efficient client products.


<<:  iOS 15.6 official version battery life test is out, it consumes more power

>>:  How big is the difference between iOS 15.4.1 and iOS 15.6? Is it worth upgrading?

Recommend

Case + Method | How to build a user growth system for cash loan products

There are many sub-sectors of Internet finance pr...

How do frogs swallow food? They push with their eyeballs!

Editor’s Note: After the comprehensive science ex...

SEO plan planning, how do SEO practitioners formulate plans?

Some time ago, a friend asked me on WeChat, if I ...

A comprehensive summary of Android application publishing platforms!

During the development process, you may encounter...

Has Bilibili fallen?

On December 8, CCTV News officially settled in Bi...

Capital cooling, it’s time to tear off the veil of “pseudo-intelligence”

Recently, a list of the death of smart hardware w...

50 marketing models that planners must have in 2022

I have the model in hand and the idea. If you don...

How to write a product promotion plan?

Entering 2018, major companies have begun to form...

Scammers are targeting tax refunds! Be careful of these "scams"!

The annual settlement of personal income tax for ...