Individuals think that the advantages of single page applications are quite obvious:
- Separation of responsibilities between front and back, clear architecture: front-end interactive logic, backend responsible for data processing.
- The front and back ends are individually developed and tested separately.
- A good interaction experience, the front-end is a partial rendering. Avoids unnecessary jumps and repeated rendering.
Of course, the SPA also has its own shortcomings, such as not conducive to search engine optimization, and so on, these problems have their corresponding solutions.
The approach described below can be said to be a pattern or workflow, independent of what framework the front-end uses, and what language and database the backend uses. Not to say the best practice, I believe that after more people's discussion and thinking there will be a Better practice. :)
Overview
Shows the entire front and back of this pattern and the main components of each:
It looks a bit complicated, and the next step is to explain each of the above sections carefully. After reading this article, you should be able to understand the interaction between the various parts of the process.
Front-end architecture
The front-end sections are individually extracted for research:
The front end is roughly divided into four types of modules:
- Component: Front-End UI components
- Services: Front-end data caching and operations layer
- Databus: Encapsulates a series of Ajax operations, and the backend for data interaction parts
- Common/utils: Common parts of the above components, reusable functions, data, etc.
Components
Component refers to a reusable UI interaction unit on a page, such as a blog commenting feature:
We can make blog comments as a component, this component has its own structure (HTML), appearance (CSS), interactive logic (JS), so we can do a separate call comment component, composed of the following files:
- Comment.html
- Comment.css
- Comment.js
(each component can be imagined as a project, even with its own readme, test, etc.)
Components Tree
A component can rely on another component, when they are father-son relationships, and component can be combined with each other, they are fraternal relationships. The final result is similar to Dom Tree,component, which can form component tree.
For example, now you want to add two features to this blog:
- Show comment reply.
- The user's business card can be displayed on the user's avatar with the mouse on the comment or reply.
We build two components, reply and User-info-card. Because each comment has its own reply list, the comment component is dependent on the reply component, and the comment and reply components are nested relationships.
And User-info-card can appear in the comment or reply, and in order to make User-info-card reusability more powerful later, it should not belong to any component, it and other components is a combination of relationships. So we're going to get a simple componenets tree:
Communication between components
How can you do the mouse on the comment and reply to the user picture on the display card? This actually involves the problem of how the components communicate with each other.
The best way to do this is to use an event mechanism, where information can be interacted between all components through a generic component called Eventbus. So, to do the above functions:
- User-info-card can listen for an event in Eventbus
user-info-card:show .
- The component can use Eventbus to trigger events when the mouse is placed on the avatar of the comment and reply components
user-info-card:show .
User-info-card:
var eventbus = require("eventbus")eventbus.on("user-info-card:show", function(user) { // 显示用户名片})
Comment or reply:
var eventbus = require("eventbus")$avatar.on("mouseover", function(event) { eventbus.emit("user-info-card:show", userData)})
The advantage of communicating between components with events is that:
- There is no strong dependency between components, and the components are decoupled from each other.
- Components can be individually developed and tested separately. Data and events can be easily falsified for testing (mocking).
Summary: There are nested and combined relationships between component, which constitute the exchange of information and data between components tree;component through events.
Services
The rendering and display of component depends on the data (model). For example, the comments above will have a model of a list of comments.
comments: [ {user:.., content:.., createTime: ..}, {user:.., content:.., createTime: ..}, {user:.., content:.., createTime: ..}]
The component of each comment is rendered for a comment (an object in the comments array), which is displayed correctly on the page after rendering.
Because this data may also be needed in other component, comment component will not save these comment model directly. The model will be stored in the service, and component will fetch the data from the service. There is a many-to-many relationship between components and services: A component may fetch data from different services, and a service may provide data for multiple components.
In addition to caching data, Services provides a number of operational interfaces to the data. Can be provided to the components for operation. The advantage is that it keeps the data constant, and if you are using the MVVM framework for component development, the manipulation of the data can also directly generate data binding on multiple views, and when the data in services changes, multiple components of the view are updated accordingly.
Summary: Services is the caching and manipulation of front-end data (that is, model).
Databus
And where does the data cached in services come from? Of course, the first scenario you might think of is to send AJAX requests directly to the server to pull the data out of the servers. Instead of doing this directly, the interface that interacts with the backend APIs is encapsulated in a module called Databus, where the databus is equivalent to "a collection of atomic operations on the backend data".
As the above comment service needs to pull data from the backend, it does this:
var databus = require("databus")var comments = nulldatabus.getAllComments(function(cmts) { // 调用databus方法进行数据拉取 comments = cmts})
And the Databus encapsulates a layer of Ajax
function(callback) { utils.ajax({ url: "/comments", method: "GET", success: callback })}
This is done because different services may use the same interface to manipulate the backend, encapsulating the operation to improve the reusability of the interface. Note that if some operations in Databus do not involve servcies data, this operation can also be called by components (such as exit, login, etc.).
Summary: Databus encapsulates an interface that provides interaction between the services and the component and backend APIs.
Common/utils
All two modules can be relied upon by other components.
Common, hence the name Incredibles, the shared data between components and some program parameters can be cached here.
Utils, encapsulates some reusable functions, such as Ajax.
Eventbus
An interface for data and message communication through an event mechanism for all components, especially between parts. This library can be implemented simply by using the eventemitter.
Back-end architecture
Traditional web pages are usually rendered by the backend, and in our architecture, the backend renders only one page, after which the backend is just the equivalent of a web Service, and the front end uses AJAX to call its interface for data transfer and manipulation, using the data to render the page.
The advantage is that the backend not only handles requests for web-side pages, but also handles requests for mobile, desktop-side or as a third-party open interface for use. Greatly improves the flexibility of back-end processing requests.
The backend is much simpler to compare with the front-end architecture, but this is just one of the patterns that may be adjusted for applications of varying degrees of complexity. The back end is roughly divided into three layers:
- CGI: Set different routing rules, accept requests from the front end, process data, and return results.
- Business: This layer encapsulates some of the operations on the database, and business can be called by CGI.
- Database: The data is persisted.
For example, in the above example of comments, CGI can receive a request sent by the front-end:
var commentsBusiness = require("./businesses/comments")app.get("/comments", function(req, res) { // 此处调用comments的business数据库操作 commentsBusiness.getAllComments(function(comments) { // 返回数据结果 res.json(comments) })})
The backend API can take the form of a more standard restful API, while restful is not covered in this article. Interested can refer to best practices for designing a pragmatic RESTful API.
The front and back of the architecture are basically clear, let's look at the beginning of the article diagram:
Looking at the diagram, let's summarize the entire front-end interaction process:
- The front end requests the first page from the server, and back-end rendering returns.
- The front-end loads each component,components from services to fetch data, and the services sends AJAX requests back to the data via Databus.
- The backend CGI receives the request sent by the front-end databus, processes the data, invokes the business Operations database, and returns the result.
- The front end receives the result returned by the backend, caches the data to the service,component to get the data to render and display the front-end component.
Work flow
A good workflow can make development more effective. The single-page application above also has a corresponding development workflow, which is also suitable for non-single-page applications:
- Perform product function and prototype design.
- Back-End Database design.
- According to the product to determine the front and back of the API (or RESTful API), document records.
- The front and back end can be developed for the API documentation at the same time.
- The front and rear end of the connection test.
Front-end separation development. Recommendations can be individually tested and independently developed using TDD (test-driven development), which can be discussed separately for web app testing, improving product reliability and stability.
Single page App Spa architecture