background

Although the storage, access, conversion and output of data in back-end applications belong to the scope of back-end applications, the frequency of change is different. Usually, once the domain objects are identified, there are very few changes, but the client displays a lot of changes, resulting in the interface layer (or glue layer between foreground and background) changing very quickly. Most Web applications use the same technology stack for the backend, glue layer and domain layer use the same technology, so there are still points that can be optimized:

  • In a pre-release environment, code construction, deployment, and verification are inefficient; Multiple people share a deployment environment, causing mutual interference and increasing costs. Back-end developers crave an independent, efficient development environment, much like developing a front-end page

  • Foreground and background technologies are different, so it is difficult for foreground students to develop back-end programs. Xianyu Technology team pursues higher development efficiency, hoping to cross the boundary between server-side development and foreground, so that foreground developers can also write back-end codes

  • The glue layer usually relies on many back-end services and is an IO intensive task. Our ideal programming framework could be as simple as writing synchronous code, but enjoy the benefits of asynchrony

Why Dart

The Idle Fish technology team chose to use DART as the glue layer implementation language.

  • Flutter has proven dart’s success in client development. Xianyu is not only at the forefront of FLUTTER development, but is also trying to use DART to develop back-end applications. Dart is a statically typed, generic-supported object-oriented language. Dart’s syntax is similar to that of mainstream development languages (Java, Python, C/C ++, javascript), and dart adds getter/setter, method cascade, functional programming, and closures to make it easier for developers to write concise code. Some describe the language as stupid-simple to learn, which allows all technical students to develop back-end interfaces at Xianyu.

  • Dart’s good support for asynchrony is a powerful boost to business development. For most IO intensive tasks, asynchronous techniques can be used to reduce the total RT of multiple IO requests from the sum of all requests to the highest RT of all requests. Dart has good support for asynchrony, and developers can use dart to achieve asynchronous performance in a near-synchronous code style. Let’s compare the different coding methods with the example of the code in the details page of The Free Fish Baby.


     

    // Java synchronizes code

    ItemDetailDO queryItemDetail(Long itemId) {

    ItemDetailDO data = new ItemDetailDO();

    data.setBrowseCount(IdleItemBrowseService.count(itemId)); // How many people have seen it

    data.setFavorCount(IdleItemFavorService.count(itemId)); // How many thumbs up

    return data;

    }

    // Dart asynchronous code

    ItemDetailDO queryItemDetail(int itemId) async {

    var data = new ItemDetailDO();

    await Future.wait([

    IdleItemBrowseService.count(itemId).then((count) => data.browseCount = count)

    .catchError((exp, stackTrace) => logError('$exp $stackTrace')),

    IdleItemFavorService.count(itemId).then((count) => data.favorCount = count)

    .catchError((exp, stackTrace) => logError('$exp $stackTrace'))

    ]);

    return data;

    }

    // RxJava asynchronous code

    ItemDetailDO queryItemDetail(Long itemId) {

    ItemDetailDO data = new ItemDetailDO();

    Flowable<Long> browseCountFlow = Flowable.fromCallable(

    () => IdleItemBrowseService.count(itemId)

    ).onErrorReturn(t -> 0).subscribeOn(Schedulers.io());

    Flowable<Long> favorCountFlow = Flowable.fromCallable(

    () => IdleItemFavorService.count(itemId)

    ).onErrorReturn(t -> 0).subscribeOn(Schedulers.io());

    Flowable.zip(browseCountFlow, favorCountFlow, (browseCount, favorCount) -> {

    data.setBrowseCount(browseCount);

    data.setFavorCount(favorCount);

    }).blockingFirst();

    }

Copy the code

In Java, we also widely use RxJava, a powerful reactive extension for asynchronous operations. Note that both service calls are run in an IO thread pool, which is unbounded and tends to consume scarce resources such as threads. This means that when traffic is very high, the system’s thread pool can easily be filled, and a proper backpressure strategy needs to be set.

You can see that the Dart code logic is very clear, unlike synchronous code scattered all over the place; Dart’s asynchronous operation allows us to wait for multiple IO events at the same time compared to synchronous operation, reducing the overall response time. Dart’s asynchronous code has the simplicity and ease of understanding of synchronous code with the performance benefits of asynchronous programming. The principle of DART asynchrony is also easy to understand. As a single-threaded language, DART relies on event loops to run code. Dart starts with the main function, where we create the Future, which is equivalent to adding scheduled tasks to an event queue that DART maintains (the added tasks will not be executed immediately). After executing the code in Main, the Dart event loop begins to retrieve task executions from the event queue. Async /await is the syntactic sugar of dart and allows developers to implement asynchronous programming in the same way they write synchronous code (there are similar implementations in C#, javascript). A method decorated with async returns a Future. Calling such a method creates a Future. To await a Future is to package the code after the await into a future.then () block, which guarantees that the code after the await will be executed after the Future. Since tasks are stored in event queues, DART consumes a large amount of memory when traffic is high, which requires us to reasonably assess requirements and allocate system resources in the early stage.

Dart backend development in action

To improve development efficiency, we leveraged DART’s features to build an efficient, isolated development environment. In the business development practice, we summarized the basic development architecture and code pattern. On the basis of these technologies, the main business of details page of Idle Fish baby has been developed. Let’s take a look at each of them.

Efficient isolated development environment

Our previous development scenarios were: Commit code -> Code conflicts (multiple people sharing a deployment environment) -> Build/deploy -> Validate through the interface -> Commit fix -> Build/deploy -> Validate iterations. During this process, developers may need to resolve code conflicts themselves, or rely on others to resolve code conflicts, requiring build/deployment time (from 5 minutes to more than 10 minutes). And the process can be iterative and time costly, multiplying the wait for validation if the deployment fails because of a problem with another developer’s branch of code. Such development efficiency is obviously not ideal. With the Dart application of idle Fish, this problem is mitigated. Each developer uses his own separate development environment, which is uniquely identified by each person’s id. Without submitting the code, developers deploy the code to a remote pre-delivery environment, call the pre-delivery service locally, and view the output of the service to achieve the effect of local verification debugging. In implementation, each developer’s independent development environment corresponds to one ISOLATE on the pre-release machine. Dart’s ISOLATE acts as a thread, but does not share memory with other isolates. The technology team used each developer’s code to create an ISOLATE. Using an employee number as an identifier, the code could either replace the active ISOLATE in full, or use hot deployment increments to replace the changed functions in the ISOLATE. The whole process is very fast. After early use of the DART native compiler, which was slow (10 + seconds), we tailored and optimized the Dart compiler to reduce compilation times from 10 + seconds to a few hundred milliseconds. (In short, we repackaged the additional functionality of the Dart native compiler and then JIT/AOT generated a new compilation tool.) After our enhancement of the dart development environment, now developed dart of glue layer interface, only need to click a button on the development tools, can put the modified code, deployment in a few seconds to remote the pretest probability of environment, and calls the development of the current interface, view the output locally, get the same effect and verification on the pretest environment, The experience is like developing a native application with no external dependencies at all.

Business development Architecture

The most important part of business development is to separate the changing parts from the unchanging parts. The changing parts should be implemented in the most flexible and fast way (of course, the most changing parts should be dealt with in the fastest way), and the unchanging parts should be implemented in a stable and efficient way. We have built DART into a technology that can be developed efficiently and used by the client, front-end, and back-end technicians. This technique is best applied to the rapidly changing interface layer, where the client and back end interact, where changing business requirements cause rapid changes in data structures, also known as the glue layer. For relatively stable data services, we use Java implementations to serve the domain.The diagram above shows the interaction between services, as shown below:

The GLUE layer DART application is invoked by the client using the HTTP protocol as the MTOP interface, and then gets data from the Java application using HSF. The domain service is usually defined and developed first, and then interconnects with the client to develop the interface. The interface provided by the domain service contains all the methods to obtain the basic data, and rarely changes after development. The glue layer obtains the data provided by the domain service, processes, cuts and assemples the data, and outputs the view data that can be parsed by the client. The client parses, renders and displays the data as pages. The code for the glue layer can be roughly divided into: data acquisition, data processing and assembly. The code pattern is abstracted as follows:


     

    // Data processing and assembly

    void putTiger(Zoo zoo, Tiger tiger) => zoo.tiger = tiger;

    void putDophin(Zoo zoo, Dophin dophin) => zoo.dophin = dophin;

    void putRatel(Zoo zoo, Ratel ratel) => zoo.ratel = ratel;

    // Make multiple asynchronous requests and return all data when all requests are completed

    Future<T> catchError<T>(Future<T> future) {

    return future.catchError((e, st) => LogUtils.error('$e $st'));

    }

    Future<List<T>> waitFutures<T>(List<Future<T>> futures) {

    Future<List<T>> future = Future.wait(futures.map(catchError));

    return catchError(future);

    }

    // Service interface

    Future<Zoo> process(Parameter param) async {

    var zoo = new Zoo();

    // Data acquisition

    await waitFutures(

    Service1.invoke(param).then((animal) -> putTiger(zoo, animal)),

    Service2.invoke(param).then((animal) -> putLion(zoo, dophin)),

    Service3.invoke(param).then((animal) -> putRatel(zoo, animal))

    );

    return finalData;

    }

Copy the code

In order to use Java domain services, we first solve the data interaction between DART and Java, mainly through serialization to reasonably convert the Java class file and DART class file, to ensure that DART can transparently and concisely use Java data structure to call Java remote services. Set a globally unique context ID on the call link, across the DART and Java call stacks, and support full link verification. There are detailed logs for all service success rates, RT and additional business parameters, you can configure logs to monitor alarms and so on (please stay tuned for more details on our solutions to these issues in future articles).

Servitization detail page trunk development

The Free Fish Baby details page is an important project we developed with DART. The original Fishbaby detail pages coupled the code logic of each business, making maintenance and change difficult and stability difficult to guarantee. The SwaK framework we designed (see the article SwaK Framework for more details) is able to separate the commonalities and differences of vertical business, splitting the implementation of the Freefish Baby detail page into a backbone implementation and a vertical business implementation. We did a minimal implementation of the SWAK framework using our own DART back-end development framework. The project completed the full functionality and basic optimization of the detail page trunk:

  • Vertical business routing: We use the zone in DART to store the business id of each idle fish commodity, and the static proxy class generated by the code invokes the corresponding service based on the business ID, filling the trunk data with data unique to each business. The zone is the execution environment for the DART asynchronous code and can cache some reusable data (don’t use it in business code unless you absolutely have to)

  • As a provider of remote services: Dart can also be a provider of remote services based on HSFCPP’s implementation of the Hessian protocol

  • Optimization of service invocation: The proxy of Java remote service has been optimized to isolate the perception of business layer to framework layer and make transparent invocation

  • We rely on the c++ interface of the cache to access the cache, but we still need to deal with incompatible Java /c++ cache reads and writes.

The actual effect

At present, the project has been online for more than 6 weeks, with a maximum QPS of 400 and a success rate of more than 99.5%. The RT of the entire invocation link is equivalent to that of a Java application with the same functionality. Due to careful design up front, domain services are rarely changed, with most of the changes occurring in the DART glue layer. The dart glue layer took less than two minutes from the end of the code modification to the end of the client’s use, compared to more than 10 minutes for a Java application with the same functionality.

conclusion

Dart is a concise, easy-to-use programming language with good asynchronous support that shines in the development of Flutter. As a result of our efforts, DART support for back-end development has improved, enabling both front end developers and back-end developers to quickly and efficiently develop the glue layer interface. In the future, we will focus on further improving the DART development experience, compatibility with Java projects, improving the performance of dart remote services, and exploring the greater potential of DART in back-end development.


You might like it

Xianyu technology selected “recommendation book list”, take away no thanks

Has the Flutter behind open source | 200 million user application framework Fish story

Blockbuster series | “UI2Code” intelligent generating code Flutter

Old code more = over-coupling =if else? “Engineer Ali said