diff --git a/docs/source/en/advanced/cluster-client.md b/docs/source/en/advanced/cluster-client.md index bb4d65f14d..19f62df341 100644 --- a/docs/source/en/advanced/cluster-client.md +++ b/docs/source/en/advanced/cluster-client.md @@ -16,21 +16,21 @@ In the previous [Multi-Process Model chapter](../core/cluster-and-ipc.md), we co +--------+ +--------+ ``` -In order to reuse persistent connections as much as possible (because they are very valuable resources for server), we put it into the Agent process to maintain, and then we transmit data to each Worker via messenger. It's feasible but often we need to write a lot of code to encapsulate the interface and to realize data transmission, which is very troublesome. +In order to reuse persistent connections as much as possible (because they are very valuable resources for server), we put them into the Agent process to maintain, and then we transmit data to each Worker via messenger. It's feasible but we often need to write many codes to encapsulate the interface and realize data transmission, which is very troublesome. In addition, it's relatively inefficient to transmit data via messenger, since messenger will do the transmission through the Master; In case IPC channel goes wrong, it would probably break Master process down. -Then is there any better way? The answer is yes. We provide a new type of model to reduce the complexity of this type of client encapsulation. The new Model bypasses the Master by establishing a direct socket between Agent and Worker. And as an external interface, Agent maintains shared connection among multiple Worker processes. +So is there any better way? The answer is: YES! We provide a new type of model to reduce the complexity of this type of client encapsulation. The new Model bypasses the Master by establishing a direct socket between Agent and Worker. And as an external interface, Agent maintains shared connection among multiple Worker processes. ## core idea -- Inspired by the [Leader/Follower](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf) model -- The client is divided into two roles: -  - Leader: responsible for maintaining the connection with the remote server, only one Leader for the same type of client. -  - Follower: delegates specific operations to the Leader. A common way is Subscribe-Model (let the Leader interact with remote server and wait for its return). +- Inspired by the [Leader/Follower](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf) model. +- The client is divided into two roles: + - Leader: Be responsible for maintaining the connection with the remote server, only one Leader for the same type of client. + - Follower: Delegate specific operations to the Leader. A common way is Subscribe-Model (let the Leader interact with remote server and wait for its return). - How to determine who Leader is, who Follower is? There are two modes: -  - Free competition mode: clients determine the Leader by the competition of the local port when start up. For example: every one tries to monitor port 7777, and finally the only one instance who seizes it will become Leader, the rest will be Followers. -  - Mandatory mode: the framework designates a Leader and the rest are Followers. + - Free competition mode: clients determine the Leader by the competition of the local port when start up. For example: every one tries to monitor port 7777, and finally the only one instance who seizes it will become Leader, the rest will be Followers. + - Mandatory mode: the framework designates a Leader and the rest are Followers. - we use mandatory mode inside the framework. The Leader can only be created inside the Agent, which is also in line with our positioning of the Agent. - When the framework starts up, Master will randomly select an available port as the communication port monitored by the Cluster Client, and passes it by parameter to Agent and App Worker. - Leader communicates with Follower through direct socket connection (through communication port), no longer needs Master to transit. @@ -60,9 +60,9 @@ win / +------------------+ \ lose We abstract the client interface into the following two broad categories, which is also a specification of the client interface. For clients that are in line with norms, we can automatically wrap it as Leader / Follower mode. -- Subscribe / Publish Mode -  - The `subscribe(info, listener)` interface contains two parameters. The first one is the information subscribed and the second one is callback function for subscribe. -  - The `publish(info)` interface contains a parameter which is the information subscribed. +- Subscribe / Publish Mode: + - The `subscribe(info, listener)` interface contains two parameters. The first one is the information subscribed and the second one is callback function for subscribe. + - The `publish(info)` interface contains a parameter which is the information subscribed. - Invoke Mode, supports three styles of interface: callback, promise and generator function, but generator function is recommended. Client example @@ -131,7 +131,7 @@ Leader and Follower exchange data via the following protocols: ``` 1. On the communication port Leader starts a Local Server, via which all Leaders / Followers communicate. -2. After Follower connects Local Server, it will firstly send a register channel packet (introduction of the channel concept is to distinguish between different types of clients) +2. After Follower connects Local Server, it will firstly send a register channel packet (introduction of the channel concept is to distinguish between different types of clients). 3. Local Server will assign Follower to a specified Leader (match based on client type). 4. Follower sends requests to Leader to subscribe and publish. 5. Leader notifies Follower through the subscribe result packet when the subscription data changes. @@ -241,7 +241,7 @@ class RegistryClient extends Base { module.exports = RegistryClient; ``` -- The second step is to encapsulate the RegistryClient using the `agent.cluster` interface. +- The second step is to encapsulate the `RegistryClient` using the `agent.cluster` interface: ```js // agent.js @@ -260,7 +260,7 @@ module.exports = agent => { }; ``` -- The third step, use the `app.cluster` interface to encapsulate RegistryClient +- The third step, use the `app.cluster` interface to encapsulate `RegistryClient`: ```js // app.js @@ -292,9 +292,9 @@ module.exports = app => { }; ``` -Is not it simple? +Isn't it so simple? -Of course, if your client is not so 『standard』, then you may need to use some other APIs, for example, your subscription function is not called subscribe, but sub. +Of course, if your client is not so 『standard』, then you may need to use some other APIs, for example, your subscription function is not named `subscribe`, but `sub`: ```js class MockClient extends Base { @@ -324,7 +324,7 @@ class MockClient extends Base { } ``` -You need to set it manually with the delegate API +You need to set it manually with the `delegate` API: ```js // agent.js @@ -358,20 +358,20 @@ module.exports = app => { }; ``` -We've already known that using cluster-client allows us to develop a 『pure』 RegistryClient without understanding the multi-process model. We can only focus on interacting with server, and use the cluster-client with a simple wrap to get a ClusterClient which supports multi-process model. The RegistryClient here is actually a DataClient that is specifically responsible for data communication with remote service. +We've already known that using `cluster-client` allows us to develop a 『pure』 RegistryClient without understanding the multi-process model. We can only focus on interacting with server, and use the `cluster-client` with a simple wrap to get a `ClusterClient` which supports multi-process model. The `RegistryClient` here is actually a `DataClient` that is specifically responsible for data communication with remote service. -You may have noticed that the ClusterClient brings with several constraints at the same time. If you want to expose the same approach to each process, RegistryClient can only support sub/pub mode and asynchronous API calls. Because all interactions in multi-process model must use socket communications, under which it is bound to bring this constraint. +You may have noticed that the `ClusterClient` brings with several constraints at the same time. If you want to expose the same approach to each process, `RegistryClient` can only support sub/pub mode and asynchronous API calls. Because all interactions in multi-process model must use socket communications, under which it is bound to bring this constraint. -Suppose we want to realize a synchronous get method. Put subscribed data directly into memory and use the get method to return data directly. How to achieve it? The real situation may be more complicated. +Suppose we want to realize a synchronous `get` method. Put subscribed data directly into memory and use the `get` method to return data directly. How to achieve it? The real situation may be more complicated. -Here, we introduce an APIClient best practice. For modules that have requirements of synchronous API such as reading cached data, an APIClient is encapsulated base on RegistryClient to implement these APIs that are not related to interaction with the remote server. The APIClient instance is exposed to the user. +Here, we introduce an `APIClient` best practice. For modules that have requirements of synchronous API such as reading cached data, an `APIClient` is encapsulated base on RegistryClient to implement these APIs that are not related to interaction with the remote server. The `APIClient` instance is exposed to the user. -In APIClient internal implementation: +In `APIClient` internal implementation: - To obtain data asynchronously, invoke RegistryClient's API base on ClusterClient. -- Interfaces that are unrelated to server, such as synchronous call, are to be implemented in APIClient. Since ClusterClient's APIs have flushed multi-process differences, there is no need to concern about multi-process model when calls to RegistryClient during developing APIClient. +- Interfaces that are unrelated to server, such as synchronous call, are to be implemented in `APIClient`. Since ClusterClient's APIs have flushed multi-process differences, there is no need to concern about multi-process model when calls to RegistryClient during developing `APIClient`. -For example, add a synchronous get method with buffer in the APIClient module: +For example, add a synchronous get method with buffer in the `APIClient` module: ```js // some-client/index.js @@ -489,8 +489,8 @@ in conclusion: ``` - RegistryClient - responsible for communicating with remote service, to access data, supports for asynchronous APIs only, and does't care about multi-process model. -- ClusterClient - a client instance that is simply wrapped by the cluster-client module and is responsible for automatically flushing differences in multi-process model. -- APIClient - internally calls ClusterClient to synchronize data, without the need to concern about multi-process model and is the final exposed module for users. APIs are exposed Through this, and support for synchronization and asynchronization. +- ClusterClient - a client instance that is simply wrapped by the `cluster-client` module and is responsible for automatically flushing differences in multi-process model. +- APIClient - internally calls `ClusterClient` to synchronize data, without the need to concern about multi-process model and is the final exposed module for users. APIs are exposed Through this, and support for synchronization and asynchronization. Students who are interested may have look at [enhanced multi-process development model](https://github.com/eggjs/egg/issues/322) discussion process. @@ -515,7 +515,7 @@ transcode | function | N/A | Serialization of interproc The above is about global configuration. If you want to do a separate setting for a client: -- You can override by setting the second argument `options` in` app/agent.cluster(ClientClass, options) ` +- You can override by setting the second argument `options` in `app/agent.cluster(ClientClass, options)`: ```js app.registryClient = app.cluster(RegistryClient, { @@ -525,7 +525,7 @@ app.registryClient = app.cluster(RegistryClient, { }); ``` -- You can also override the `getter` attribute of `clusterOptions` in `APIClientBase`. +- You can also override the `getter` attribute of `clusterOptions` in `APIClientBase`: ```js const APIClientBase = require('cluster-client').APIClientBase; diff --git a/docs/source/en/advanced/framework.md b/docs/source/en/advanced/framework.md index 4e10de9749..b1f5959209 100644 --- a/docs/source/en/advanced/framework.md +++ b/docs/source/en/advanced/framework.md @@ -1,32 +1,32 @@ title: Framework Development --- -During maintaining a number of projects, are your familiar with situations below: +If your team have met with these scenarios: - Each project contains the same configuration files that need to be copied every time, such as `gulpfile.js`, `webpack.config.js`. -- Each project has similiar dependancies. -- It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have changed? +- Each project has similiar dependencies. +- It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have been optimized? -Have your team got: +If your team needs: - a unified technique selection, such as the choice of databases, templates, frontend frameworks, and middlewares. - a unified default configuration to balance the deviation of different situations, which are not supposed to resolve in code level, like the differences between companies and open communities. - a unified [deployment plan](../core/deployment.md) keeping developers concentrate on code without paying attention to deployment details of connecting the framework and platforms. - a unified code style to decrease code's repetition and optimize code's appearance, which is important for a enterprise level framework. -To satisfy these demands, Egg endows developers with the capacity of `customazing a framework`. It is just an abstract layer, which is able to construct a higher level framework, supporting inheritance of unlimited times. Futhermore, Egg apply a quantity of coding conventions based on Koa. +To satisfy these demands, Egg endows developers with the capacity of `customazing a framework`. It is just an abstract layer, which can be constructed to a higher level framework, supporting inheritance of unlimited times. Futhermore, Egg apply a quantity of coding conventions based on Koa. -Therefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can continuously extracted from these plugins to the framework, which is available to other project by just updating the dependances' version. +Therefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can be continuously extracted from these plugins to the framework, which is available to other projects by just updating the dependencies' versions. See more details in [Progressive Development](../tutorials/progressive.md)。 -## Framework And Multiprocess +## Framework and Multiprocess -The framework extension is applied to Multiprocess Model, as we know [Multiprocess Model](../core/cluster-and-ipc.md) and the diffirences between Agent Worker and App Worker, which have diffirent API and both need to inherit. +The framework extension is applied to Multiprocess Model, as we know [Multiprocess Model](../core/cluster-and-ipc.md) and the differences between Agent Worker and App Worker, which have different APIs and both need to inherit. They both are inherited from [EggCore](https://github.com/eggjs/egg-core), and Agent is instantiated during the initiation of Agent Worker, while App is instantiated during the initiation of App Worker. -We could think about EggCore as the advanced version of Koa Application, which integrates built-in features such as [Loader](./loader.md)、[Router](../basics/router.md) and asynchronous launch. +We could regard EggCore as the advanced version of Koa Application, which integrates built-in features such as [Loader](./loader.md)、[Router](../basics/router.md) and asynchronous launch. ``` Koa Application @@ -40,7 +40,7 @@ We could think about EggCore as the advanced version of Koa Application, which i agent worker app worker ``` -## How To Customize A Framework +## How to Customize a Framework To setup by [egg-init](https://github.com/eggjs/egg-init) with the [framework](https://github.com/eggjs/egg-boilerplate-framework) option is a good way, in which generates a scaffold for you. @@ -134,7 +134,7 @@ As a loadUnit of framework, yadan is going to load specific directories and file ### Principle of Framework Extension -The path of framework is set as a varible named as `Symbol.for('egg#eggPath') to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessiable through the prototype chain. +The path of framework is set as a varible named as `Symbol.for('egg#eggPath')` to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessiable through the prototype chain. Given a triple-layer framework: department level > enterprise level > Egg @@ -164,7 +164,7 @@ const app = new Application(); app.ready(); ``` -These code are pseudocode to elaborate the framework's loading process, and we have provided scaffolds to [development](../core/development.md)和[deployment](../core/deployment.md). +These code are pseudocode to elaborate the framework's loading process, and we have provided scaffolds to [development](../core/development.md) and [deployment](../core/deployment.md). ### Custom Agent @@ -196,7 +196,7 @@ module.exports = Object.assign(egg, { }); ``` -** To be careful about that Agent and Application based on the same Class possess different APIs. ** +**To be careful about that Agent and Application based on the same Class possess different APIs.** ### Custom Loader @@ -371,5 +371,3 @@ describe('/test/index.test.js', () => { }); }); ``` - -[egg-bin]: https://github.com/eggjs/egg-bin diff --git a/docs/source/en/advanced/loader.md b/docs/source/en/advanced/loader.md index 367bae01ff..84d8cf5461 100644 --- a/docs/source/en/advanced/loader.md +++ b/docs/source/en/advanced/loader.md @@ -1,11 +1,11 @@ title: Loader --- -The most important of Egg enhance Koa is Egg based on a certain agreement, code will be placed in different directories according to the functional differences, it significantly reduce development costs. Loader supports this set of conventions and abstracts that many low-level APIs could be extended. +The most importance of Egg which enhanced Koa is that it is based on a certain agreement, code will be placed in different directories according to the functional differences, it significantly reduces development costs. Loader supports this set of conventions and abstracts that many low-level APIs could be extended. ## Application, Framework and Plugin -Egg is a low-level framework, applications could use it directly, but Egg only has a little default plugins, applications need to configure plugins to extend features, such as MySQL. +Egg is a low-level framework, applications could use it directly, but Egg only has a few default plugins, we need to configure plugins to extend features in application, such as MySQL. ```js // application configuration @@ -26,7 +26,7 @@ module.exports = { } ``` -With the increase number of applications, we find most of them have similar configurations, then we could extend a new framework based on Egg, which make the application configuration simpler. +With the increasing number of applications, we find most of them have similar configurations, so we could extend a new framework based on Egg, which makes application configurations simpler. ```js // framework configuration @@ -68,11 +68,11 @@ module.exports = { } ``` -From the above scene we can see the relationship of application, plugin and framework. +From the scene above we can see the relationship of application, plugin and framework. -- We implement business logics in application, we need to specify a framework to run, we could configure plugin to meet a special scene feature (such as MySQL). +- We implement business logics in application, and specify a framework to run, so we could configure plugin to meet a special scene feature (such as MySQL). - Plugin only performs specific function, when two separate functions are interdependent, they still need to be separated into two plugins, and requires configuration. -- Framework is a launcher (default is Egg), which is indispensable to run. Framework is also a wrapper, it aggregates plugins to provide functions unitedly, framework can also configure plugins. +- Framework is a launcher (default is Egg), which is indispensable to run. Framework is also a wrapper, it aggregates plugins to provide functions unitedly, and it can also configure plugins. - We can extend a new framework based on framework, which means that **framework can be inherited infinitely**, like class inheritance. ``` @@ -91,7 +91,7 @@ From the above scene we can see the relationship of application, plugin and fram ## loadUnit -Egg regard application, framework and plugin as loadUnit, because they are similar in code structure, here is the directory structure +Egg regards application, framework and plugin as loadUnit, because they are similar in code structure, here is the directory structure: ``` loadUnit @@ -117,7 +117,7 @@ loadUnit └── config.unittest.js ``` -However, there are still some differences +However, there are still some differences: File | Application | Framework | Plugin --- | --- | --- | --- @@ -132,13 +132,13 @@ config/config.{env}.js | ✔︎ | ✔︎ | ✔︎ config/plugin.js | ✔︎ | ✔︎ | package.json | ✔︎ | ✔︎ | ✔︎ -During the loading process, Egg will traverse all loadUnits to load the above files (application, framework and plugin are different), the loading process has priority. +During the loading process, Egg will traverse all loadUnits to load the files above(application, framework and plugin are different), the loading process has priority. -- follow the order Plugin => Framework => Application to load -- The order of loading plugins depends on the dependencies, depended plugins will be loaded first, no dependencies plugins are loaded by the object key configuration order, see [Plugin](./plugin.md) for details. +- Load according to `Plugin => Framework => Application`. +- The order of loading plugin depends on the dependencies, dependent plugins will be loaded first, independent plugins are loaded by the object key configuration order, see [Plugin](./plugin.md) for details. - Frameworks are loaded by the order of inheritance, the lower the more priority. -For example, an application is configured with the following dependencies +For example, an application is configured with the following dependencies: ``` app @@ -149,7 +149,7 @@ app └── egg ``` -The final loading order is +The final loading order is: ``` => plugin1 @@ -160,13 +160,13 @@ The final loading order is => app ``` -The plugin1 is framework1 depended plugin, the object key order of plugin1 after configuration merger is prior to plugin2/plugin3. Because of the dependencies between plugin2 and plugin3, the order is swapped. The framework1 inherits the Egg, so the order it after the egg. The application is the last to be loaded. +The plugin1 is framework1's dependent plugin, the object key order of plugin1 after configuration merger is prior to plugin2/plugin3. Because of the dependencies between plugin2 and plugin3, the order is swapped. The framework1 inherits the Egg, so the order is after the egg. The application is the last to be loaded. See [Loader.getLoadUnits](https://github.com/eggjs/egg-core/blob/65ea778a4f2156a9cebd3951dac12c4f9455e636/lib/loader/egg_loader.js#L233) method for details. ### File order -The above lists default loading files, Egg will load files by the following order, each file or directory will be loaded according to loadUnit order (application, framework and plugin are different). +The files that will be loaded by default are listed above. Egg will load files by the following order, each file or directory will be loaded according to loadUnit order (application, framework and plugin are different): - Loading [plugin](./plugin.md), find application and framework, loading `config/plugin.js` - Loading [config](../basics/config.md), traverse loadUnit to load `config/config.{env}.js` @@ -177,7 +177,7 @@ The above lists default loading files, Egg will load files by the following orde - Loading [controller](../basics/controller.md), loading application's `app/controller` directory - Loading [router](../basics/router.md), loading application's `app/router.js` -Note +Note: - Same name will be override in loading, for example, if we want to override `ctx.ip` we could define ip in application's `app/extend/context.js` directly. - See [framework development](./framework.md) for detail application launch order. @@ -186,7 +186,7 @@ Note The framework will convert file names when loading files, because there is a difference between the file naming style and the API style. We recommend that files use underscores, while APIs use lower camel case. For examplem `app/service/user_info.js` will be converted to `app.service.userInfo`. -The framework also supports hyphens and camel case. +The framework also supports hyphens and camel case: - `app/service/user-info.js` => `app.service.userInfo` - `app/service/userInfo.js` => `app.service.userInfo` @@ -258,7 +258,7 @@ module.exports = Object.assign(egg, { It's convenient for development team to customize loading via the Loader supported APIs. such as `this.model.xx`, `app/extend/filter.js` and so on. -The above is just a description of the Loader wording, see [Framework Development](./framework.md) for details. +The mention above is just a description of the Loader wording, please see [Framework Development](./framework.md) for details. ## Loader API @@ -266,7 +266,7 @@ Loader also supports some low level APIs to simplify code when extending, [here] ### loadFile -Used to load a file, such as loading `app.js` is using this method. +Used to load a file, such as loading `app/xx.js`: ```js // app/xx.js @@ -282,7 +282,7 @@ module.exports = app => { }; ``` -If the file export a function, then the function will be called with app as a parameter, otherwise using this value directly. +If the file exports a function, then the function will be called with `app` as its parameter, otherwise uses this value directly. ### loadToApp @@ -297,17 +297,17 @@ module.exports = app => { }; ``` -The method has three parameters `loadToApp(directory, property, LoaderOptions)` +The method has three parameters `loadToApp(directory, property, LoaderOptions)`: -1. directory could be String or Array, Loader will load files in those directories. -1. property is app's property. -1. [LoaderOptions](#LoaderOptions) are some configurations. +1. Directory could be String or Array, Loader will load files in those directories. +2. Property is app's property. +3. [LoaderOptions](#LoaderOptions) are some configurations. ### loadToContext -The difference between loadToApp and loadToContext is that loadToContext loading files into ctx instead of app, and it is lazy loading. Putting files into a temp object when loading, and instantiate object when calling ctx API. +The difference between loadToApp and loadToContext is that loadToContext loads files into ctx instead of app, and it's a lazy loading. It puts files into a temp object when loading, and instantiates objects when calling ctx APIs. -For example, service loading is this mode +We load service in this mode as an example: ```js // The following is just an example, using loadService in practice @@ -331,14 +331,14 @@ app.loader.loadToContext(servicePaths, 'service', { }); ``` -`app.serviceClasses.user` becomes UserService after file loading, instantiating UserService when calling `ctx.service.user`. -So this class will only be instantiated when first calling, and will be cached after instantiation, multiple calling same request will only instantiating once. +`app.serviceClasses.user` becomes UserService after file loading, it instantiates UserService when calling `ctx.service.user`. +So this class will only be instantiated when first calling, and will be cached after instantiation, multiple calling same request will be instantiated only once. ### LoaderOptions #### ignore [String] -ignore could ignore some files, supports glob, the default is empty +`ignore` could ignore some files, supports glob, the default is empty. ```js app.loader.loadToApp(directory, 'controller', { @@ -349,7 +349,7 @@ app.loader.loadToApp(directory, 'controller', { #### initializer [Function] -Processing each file exported values, the default is empty. +Processing each file exported values, the default is empty. ```js // app/model/user.js @@ -372,13 +372,13 @@ app.loader.loadToApp(directory, 'model', { File conversion rules, could be `camel`, `upper`, `lower`, the default is `camel`. -All three convert file name to camel case, but deal with the initials differently. +All three convert file name to camel case, but deal with the initials differently: - `camel`: initials unchanged. - `upper`: initials upper case. - `lower`: initials lower case. -Loading different files using different configurations. +Loading different files uses different configurations: File | Configuration --- | --- @@ -388,11 +388,11 @@ app/service | lower #### override [Boolean] -Overriding or throwing exception when encounter existing files, the default is false +Overriding or throwing exception when encounter existing files, the default is false. For example, the `app/service/user.js` is both loaded by the application and the plugin, if the setting is true, the application will override the plugin, otherwise an error will be throwed when loading. -Loading different files using different configurations. +Loading different files uses different configurations: File | Configuration --- | --- @@ -404,7 +404,7 @@ app/service | false Calling when export's object is function, and get the return value, the default is true -Loading different files using different configurations. +Loading different files uses different configurations: File | Configuration --- | --- diff --git a/docs/source/en/advanced/plugin.md b/docs/source/en/advanced/plugin.md index f66514b428..aa03f886da 100644 --- a/docs/source/en/advanced/plugin.md +++ b/docs/source/en/advanced/plugin.md @@ -2,15 +2,15 @@ title: Plugin Development --- -Plugins is the most important feature in Egg framework. It keeps Egg simple, stable and efficient, and also it can make the best reuse of business logic, to build an entire ecosystem. Someone should be confused: +Plugins are the most important features in Egg framework. They keep Egg simple, stable and efficient, and also they make the best reuse of business logic to build an entire ecosystem. Maybe we want to ask: -- Since Koa already has the mechanism of middleware, what's the point of the Egg's plugins -- What is the differences between middleware, plugin and application, what is the relationship -- How can I use the plugin -- How do I build a plugin +- Since Koa already has the mechanism of middleware, Why do we need plugins? +- What are the differences / relationships among middlewares, plugins and applications? +- How can I use the plugin? +- How do I build a plugin? - ... -As we've already explained some these points in Chapter [using plugins](../basics/plugin.md) before. Now we are going through how to build a plugin. +As we've already explained some these points in chapter [using plugins](../basics/plugin.md) before. Now we are going through how to build a plugin. ## Plugin Development @@ -27,7 +27,7 @@ $ npm test ## Directory of Plugin -Plugin is actually a 'mini application', directory of plugin is as below +Plugin is actually a `mini application`, directory of plugin is as below: ```js . egg-hello @@ -56,7 +56,7 @@ Plugin is actually a 'mini application', directory of plugin is as below └── mw.test.js ``` -It is almost the same as the application directory, what's the difference? +It is almost the same as the application directory, what're the differences? 1. Plugin have no independant router or controller. This is because: @@ -64,12 +64,12 @@ It is almost the same as the application directory, what's the difference? - An application might have plenty of dependant plugins, routers of plugin are very possible conflict with others. It would be a disaster. - If you really need a general router, you should implement it as middleware of the plugin. -2. The specific information of plugin should be declared in the `package.json` of `eggPlugin`: +2. The specific information of plugin should be declared in the `package.json` of `eggPlugin`: - - `{String} name` - plugin name(required), it must be unique, it will be used in the config of the dependencies of plugin. - - `{Array} dependencies` - strong dependant plugins list of current plugin(if one of these plugins here is not found, application's startup will be failed) + - `{String} name` - plugin name(required), it must be unique, it will be used in the config of the dependencies of plugins. + - `{Array} dependencies` - strong dependent plugins list of the current plugin(if one of these plugins here is not found, application's startup will fail). - `{Array} optionalDependencies` - optional dependencies list of this plugin.(if these plugins are not activated, only warnings would be occurred, and will not affect the startup of the application). - - `{Array} env` - this option is available only when specify the environment. The list of env please refer to [env](../basics/env.md). This is optional, most time you can leave it. + - `{Array} env` - this option is available only when specifying the environment. For the list of env, please refer to [env](../basics/env.md). This is optional, most time you can leave it. ```json { @@ -90,7 +90,7 @@ It is almost the same as the application directory, what's the difference? ## Dependencies Management of Plugins -The dependencies are managed by plugin himself, this is different from middleware. Before loading plugins, application will read `eggPlugin > dependencies` and `eggPlugin > optionalDependencies` from `package.json`, and then sort out the loading orders according to their relationships, for example, the loading order of the following plugins is `c => b => a` +The dependencies are managed by plugin himself, which is different from middlewares. Before loading plugins, application will read `eggPlugin > dependencies` and `eggPlugin > optionalDependencies` from `package.json`, and then sort out the loading orders according to their relationships, for example, the loading order of the following plugins is `c => b => a`: ```json // plugin a @@ -120,16 +120,16 @@ The dependencies are managed by plugin himself, this is different from middlewar } ``` -** Attention: The values in `dependencies` and `optionalDependencies` are the `eggPlugin.name` of plugins, not `package.name`. ** +**Attention: The values in `dependencies` and `optionalDependencies` are the `eggPlugin.name` of plugins, not `package.name`.** -The `dependencies` and `optionalDependencies` is studied from `npm`, most time we are using `dependencies`, it is recommended. There are about two situations to apply the `optionalDependencies`: +The `dependencies` and `optionalDependencies` are learnt from `npm`, most time we use `dependencies`, which is recommended. There are about two situations to apply the `optionalDependencies`: -- Only be dependant in specific environment: for example, a authentication plugin, only depends on the mock plugin in development environment. -- Weakly depending, for example: A depends on B, but without B, A can take other choice +- Only be dependant in specific environment: for example, an authentication plugin, only depends on the mock plugin in development environment. +- Weakly depending, for example: A depends on B, but without B, A can take other choices. -Pay attention: if you are using `optionalDependencies`, framework won't verify the activation of these dependencies, they are only for sorting loading orders. In such situation, the plugin will go through other ways such as `interface detection` to decide processing logic. +Attention: if you are using `optionalDependencies`, framework won't verify the activation of these dependencies, they are only for sorting loading orders. In such situation, the plugin will go through other ways such as `interface detection` to decide processing logic. -## What can plugin do +## What can plugin do? We've discussed what plugin is. Now what can it do? @@ -146,7 +146,7 @@ Extend the built-in objects of the framework, just like the application ### Insert Custom Middlewares -1. First, define and implement middleware under directory `app/middleware` +1. First, define and implement middleware under directory `app/middleware`: ```js 'use strict'; @@ -167,7 +167,7 @@ Extend the built-in objects of the framework, just like the application }; ``` -2. Insert middleware to the appropriate position in `app.js`(e.g. insert static middleware before bodyParser ) +2. Insert middleware to the appropriate position in `app.js`(e.g. insert static middleware before bodyParser): ```js const assert = require('assert'); @@ -181,9 +181,9 @@ Extend the built-in objects of the framework, just like the application }; ``` -### Initialization on Application Starting +### Initialization on application starting -- If you want to read some local config before startup +- If you want to read some local config before startup: ```js // ${plugin_root}/app.js @@ -197,7 +197,7 @@ Extend the built-in objects of the framework, just like the application }; ``` -- If you want to do some async starting business, you can do it with `app.beforeStart` API +- If you want to do some async starting business, you can do it with `app.beforeStart` API: ```js // ${plugin_root}/app.js @@ -215,7 +215,7 @@ Extend the built-in objects of the framework, just like the application }; ``` -- You can add starting business of agent with `agent.beforeStart` API +- You can add initialization business of agent with `agent.beforeStart` API: ```js // ${plugin_root}/agent.js @@ -235,7 +235,7 @@ Extend the built-in objects of the framework, just like the application ### Setup Schedule Task -1. Setup dependencies of schedule plugin in `package.json` +1. Setup dependencies of schedule plugin in `package.json`: ```json { @@ -247,7 +247,7 @@ Extend the built-in objects of the framework, just like the application } ``` -2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your schedule task +2. Create a new file in `${plugin_root}/app/schedule/` directory to edit your schedule task: ```js exports.schedule = { @@ -262,18 +262,18 @@ Extend the built-in objects of the framework, just like the application }; ``` -### Best Practice of Global Instance Plugin +### Best Practice of Global Instance Plugins -Some plugins are made to introduce existing service into framework, like [egg-mysql],[egg-oss].They all need to create corresponding instance in application. We notice that there are some common problems when developing this kind of plugins: +Some plugins are made to introduce existing service into framework, like [egg-mysql], [egg-oss].They all need to create corresponding instances in applications. We notice that there are some common problems when developing plugins of this kind: -- Use different instances of the same service in one application(e.g:connect to two different MySQL Databases) -- Dynamically initialize connection after getting config from other service(gets MySQL server address from configuration center and then creates connection) +- Use different instances of the same service in one application (e.g: connect to two different MySQL databases). +- Dynamically initialize connection after getting config from other service (e.g: get the MySQL server address from configuration center and then create connection). -If each plugin makes their own implementation, all sorts of configs and initializations will be chaotic. So the framework supplies the `app.addSingleton(name, creator)` API to unify the creation of this kind of services. Note that while using the `app.addSingleton(name, creator)` method, the configuration file must have the `client` or `clients` key configuration as the `config` to the `creator` function. +If each plugin makes their own implementation, all sorts of configs and initializations will be chaotic. So the framework supplies the `app.addSingleton(name, creator)` API to unify the creation of this kind of services. Note that while using the `app.addSingleton(name, creator)` method, the configuration file must have the `client` or `clients` key configuration as the `config` to the `creator` function. -#### Writing Plugin +#### Ways of writing plugins -We simplify the [egg-mysql] plugin and to see how to write such a plugin: +We simplify the [egg-mysql] plugin to see how to write it: ```js // egg-mysql/app.js @@ -284,7 +284,7 @@ module.exports = app => { } /** - * @param {Object} config The config which already processed by the framework. If the application configured multiple MySQL instances, each config would be passed in and call multiple createMysql + * @param {Object} config The config is processed by the framework. If the application is configured with multiple MySQL instances, each config would be passed in and call multiple createMysql * @param {Application} app the current application * @return {Object} return the created MySQL instance */ @@ -303,7 +303,7 @@ function createMysql(config, app) { } ``` -The initialization function also support `Async function`, convenient for some special plugins that need to be asynchronous to get some configuration files. +The initialization function also supports `Async function`, convenient for some special plugins that need to be asynchronous to get some configuration files. ```js async function createMysql(config, app) { @@ -321,13 +321,13 @@ async function createMysql(config, app) { } ``` -As you can see, all we need to do for this plugin is passing in the field that need to be mounted and the corresponding initialization function. Framework will be in charge of managing all the configs and the ways to access the instances. +As you can see, all we need to do for this plugin is passing the fields that need to be mounted and the corresponding initialization function. Framework will be in charge of managing all the configs and the ways to access the instances. #### Application Layer Usage Case ##### Single Instance -1. Declare MySQL config in config file +1. Declare MySQL config in config file: ```js // config/config.default.js @@ -344,7 +344,7 @@ As you can see, all we need to do for this plugin is passing in the field that n }; ``` -2. Access database through `app.mysql` directly +2. Access database through `app.mysql` directly: ```js // app/controller/post.js @@ -357,7 +357,7 @@ As you can see, all we need to do for this plugin is passing in the field that n ##### Multiple Instances -1. Of course we need to configure MySQL in the config file, but different from single instance, we need to add `clients` in the config to declare the configuration of different instances. meanwhile, the `default` field can be used to configure the shared configuration in multiple instances(e.g. host and port). Note that in this case,should use `get` function to specify the corresponding instance(eg: use `app.mysql.get('db1').query()` instead of using `app.mysql.query()` directly to get a `undefined`). +1. We need to configure MySQL in the config file, but different from single instance, we need to add `clients` in the config to declare the configuration of different instances. Meanwhile, the `default` field can be used to configure the shared configuration in multiple instances (e.g: `host` and `port`). In this case, we should use `get` function to specify the corresponding instance(e.g: use `app.mysql.get('db1').query()` instead of using `app.mysql.query()` directly to get an `undefined`). ```js // config/config.default.js @@ -383,7 +383,7 @@ As you can see, all we need to do for this plugin is passing in the field that n }; ``` -2. Access the corresponding instance by `app.mysql.get('db1')` +2. Access the corresponding instance by `app.mysql.get('db1')`: ```js // app/controller/post.js @@ -396,7 +396,7 @@ As you can see, all we need to do for this plugin is passing in the field that n ##### Dynamically Instantiate -Instead of declaring the configuration in the configuration file in advance, We can dynamically initialize an instance at the runtime of the application. +Instead of declaring the configuration in the configuration file in advance, we can dynamically initialize an instance at the runtime of the application. ```js // app.js @@ -421,13 +421,13 @@ class PostController extends Controller { } ``` -**Attention, when creating the instance dynamically, framework would read the `default` configuration in the config file as the default configuration** +**Attention: when creating the instance dynamically, framework would read the `default` configuration from the config file as the default.** ### Plugin Locate Rule -When loading the plugins in the framework, it will follow the rules to locate them as below: +When loading the plugins in the framework, it will follow the rules below: -- If there is the path configuration, load them in path directly +- If there is the path configuration, load them in path directly. - If there is no path configuration, search them with the package name, the search orders are: 1. `node_modules` directory of the application root @@ -436,15 +436,15 @@ When loading the plugins in the framework, it will follow the rules to locate th ### Plugin Specification -We are very welcome your contribution to the new plugins, but also hope you follow some of following specifications: +It's well welcomed to your contributions to the new plugins, but also hope you follow some of following specifications: -- Naming Rules - - `npm` packages must append prefix `egg-`,and all letters must be lowercase,for example:`egg-xxx`. The long names should be concatenated with middle-lines:`egg-foo-bar`. +- Naming Rules: + - `npm` packages must append prefix `egg-`,and all letters must be lowercase, e.g: `egg-xxx`. The long names should be concatenated with middle-lines: `egg-foo-bar`. - The corresponding plugin should be named in camel-case. The name should be translated according to the middle-lines of the `npm` name:`egg-foo-bar` => `fooBar`. - - The use of middle-lines is not compulsive,for example:userservice(egg-userservice) and user-service(egg-user-service) are both acceptable. -- `package.json` Rules + - The use of middle-lines is not compulsive, e.g: userservice(egg-userservice) and user-service(egg-user-service) are both acceptable. +- `package.json` Rules: - Add `eggPlugin` property according to the details discussed before. - - For convenient index, add `egg`,`egg-plugin`,`eggPlugin` in `keywords`. + - For convenient index, add `egg`,`egg-plugin`,`eggPlugin` in `keywords`: ```json { @@ -472,9 +472,9 @@ We are very welcome your contribution to the new plugins, but also hope you foll Egg defines the plugin name through the `eggPlugin.name`, it is only unique in application or framework, that means **many npm packages might get the same plugin name**, why design in this way? -First, Egg plugin do not only support npm package, it also supports search plugins in local directory. In Chapter [progressive](../tutorials/progressive.md) we mentioned how to make progress by using these two configurations. Directory is more friendly to unit test. So, Egg can not ensure uniqueness through npm package name. +First, Egg plugin does not only support npm packages, but also supports plugins-searching in local directory. In Chapter [progressive](../tutorials/progressive.md) we've mentioned how to make progress by using these two configurations. Directories are more friendly to unit tests. So, Egg can not ensure uniqueness through npm package names. -What's more, Egg can use this feature to make Adapter. For example, the plugin defined in[Template Develop Spec](./view-plugin.md#PluginNameSpecification) was named as view, but there are plugins named `egg-view-nunjucks` and `egg-view-react`, the users only need to change the plugin and modify the templates, no need to modify the Controller, because all these plugins have implemented the same API. +What's more, Egg can use this feature to make an adapter, for example, the plugin defined in[Template Develop Spec](./view-plugin.md#PluginNameSpecification) was named as view, but there are plugins named `egg-view-nunjucks` and `egg-view-react`, the users only need to change the plugin and modify the templates, no need to modify the controllers, because all these plugins have implemented the same APIs. **Giving the same plugin name and the same API to the same plugin can make quick switch between them**. This is really really useful in template and database. diff --git a/docs/source/en/advanced/view-plugin.md b/docs/source/en/advanced/view-plugin.md index 1e8bc28484..13848018ed 100644 --- a/docs/source/en/advanced/view-plugin.md +++ b/docs/source/en/advanced/view-plugin.md @@ -1,8 +1,8 @@ ## Title: View Plugin Development -In most cases, we need to read the data, render the template and then present it to the user. The framework does not force the use of a template engine, allowing developers to select the [template](../core/view.md) themselves. For details, see [Template Rendering](../core/view.md). +In most cases, we need to read the data, render the template and then present it to the user. The framework does not force to use one template engine, but allows developers to select the [template](../core/view.md) by themselves. For details, see [Template Rendering](../core/view.md). -This article describes the framework's specification constraints on the View plugin, and we can use this to encapsulate the corresponding template engine plugin. The following takes [egg-view-ejs] as an example +This article describes the framework's specification constraints on the View plugin, and we can use this to encapsulate the corresponding template engine plugin. The following takes [egg-view-ejs] as an example. ## Plugin directory structure @@ -83,23 +83,23 @@ Mmdule.exports = class EjsView { ### Parameters -The three parameters of the `render` method are +The three parameters of the `render` method are: -* filename: is the path to the complete file. The framework determines if the file exists when it looks for the file. It does not need to be processed here. -* locals: The data needed for rendering. The data comes from `app.locals`, `ctx.locals` and calls `render` methods. The framework also has built in `ctx`, `request`, `ctx.helper` objects. +* filename: is the path to the complete file. The framework determines if the file exists when looking for the file. It does not need to be processed here. +* locals: The data needs rendering. It comes from `app.locals`, `ctx.locals` and calls `render` methods. The framework also has built in `ctx`, `request`, `ctx.helper` objects. * viewOptions: The incoming configuration of the user, which can override the default configuration of the template engine. This can be considered based on the characteristics of the template engine. For example, the cache is enabled by default but a page does not need to be cached. -The three parameters of the `renderString` method +The three parameters of the `renderString` method: -* tpl: template string, not file path -* locals: same with `render` -* viewOptions: same with `render` +* tpl: template string, not file path. +* locals: same with `render`. +* viewOptions: same with `render`. ## Plugin configuration -According to the naming conventions mentioned above, the configuration name is generally the name of the template engine, such as ejs +According to the naming conventions mentioned above, the configuration name is generally the name of the template engine, such as ejs. -The configuration of the plugin mainly comes from the configuration of the template engine, and the configuration items can be defined according to the specific conditions, such as the [configuration of ejs](https://github.com/mde/ejs#options) +The configuration of the plugin mainly comes from the configuration of the template engine, and the configuration items can be defined according to the specific conditions, such as the [configuration of ejs](https://github.com/mde/ejs#options). ```js // config/config.default.js @@ -120,11 +120,11 @@ In template rendering, we often need to output a user-supplied html fragment, in
{{ helper.shtml(data.content) | safe }}
``` -However, as shown in the above code, we need to use `| safe` to tell the template engine that the html is safe and it doesn't need to run `escape` again. +However, as shown in the code above, we need to use `| safe` to tell the template engine that the html is safe and it doesn't need to run `escape` again. This is more cumbersome to use and easy to forget, so we can package it: -First provide a helper subclass: +- First provide a helper subclass: ```js // {plugin_root}/lib/helper.js @@ -139,7 +139,7 @@ module.exports = app => { }; ``` -Use a custom helper when rendering +- Use a custom helper when rendering: ```js // {plugin_root}/lib/view.js @@ -158,7 +158,7 @@ You can [view](https://github.com/eggjs/egg-view-nunjucks/blob/2ee5ee992cfd95bc0 Templates and security are related and [egg-security] also provides some methods for the template. The template engine can be used according to requirements. -First declare a dependency on [egg-security] +First declare a dependency on [egg-security]: ```json { @@ -170,11 +170,11 @@ First declare a dependency on [egg-security] } ``` -The framework provides [app.injectCsrf](../core/security.md#appinjectcsrfstr) and [app.injectNonce](../core/security.md#appinjectnonncestr), for more information on [security section](../core/security.md). +Besides, the framework provides [app.injectCsrf](../core/security.md#appinjectcsrfstr) and [app.injectNonce](../core/security.md#appinjectnonncestr), for more information on [security section](../core/security.md). ### Unit tests -As a high-quality plugin, perfect unit testing is indispensable, and we also provide a lot of auxiliary tools to make it painless for plugin developers to write tests, see [unit testing](../core/unittest.md) and [plugin](./plugin.md) docs. +As a high-quality plugin, perfect unit testing is indispensable, and we also provide lots of auxiliary tools to make it painless for plugin developers to write tests with, see [unit testing](../core/unittest.md) and [plugin](./plugin.md) docs. [egg-security]: https://github.com/eggjs/egg-security [egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks diff --git a/docs/source/zh-cn/advanced/cluster-client.md b/docs/source/zh-cn/advanced/cluster-client.md index a318a9d1bb..ce2cab2856 100644 --- a/docs/source/zh-cn/advanced/cluster-client.md +++ b/docs/source/zh-cn/advanced/cluster-client.md @@ -24,16 +24,16 @@ title: 多进程研发模式增强 ## 核心思想 -- 受到 [Leader/Follower](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf) 模式的启发 +- 受到 [Leader/Follower](http://www.cs.wustl.edu/~schmidt/PDF/lf.pdf) 模式的启发。 - 客户端会被区分为两种角色: - - Leader: 负责和远程服务端维持连接,对于同一类的客户端只有一个 Leader + - Leader: 负责和远程服务端维持连接,对于同一类的客户端只有一个 Leader。 - Follower: 会将具体的操作委托给 Leader,常见的是订阅模型(让 Leader 和远程服务端交互,并等待其返回)。 - 如何确定谁是 Leader,谁是 Follower 呢?有两种模式: - 自由竞争模式:客户端启动的时候通过本地端口的争夺来确定 Leader。例如:大家都尝试监听 7777 端口,最后只会有一个实例抢占到,那它就变成 Leader,其余的都是 Follower。 - - 强制指定模式:框架指定某一个 Leader,其余的就是 Follower + - 强制指定模式:框架指定某一个 Leader,其余的就是 Follower。 - 框架里面我们采用的是强制指定模式,Leader 只能在 Agent 里面创建,这也符合我们对 Agent 的定位 -- 框架启动的时候 Master 会随机选择一个可用的端口作为 Cluster Client 监听的通讯端口,并将它通过参数传递给 Agent 和 App Worker -- Leader 和 Follower 之间通过 socket 直连(通过通讯端口),不再需要 Master 中转 +- 框架启动的时候 Master 会随机选择一个可用的端口作为 Cluster Client 监听的通讯端口,并将它通过参数传递给 Agent 和 App Worker。 +- Leader 和 Follower 之间通过 socket 直连(通过通讯端口),不再需要 Master 中转。 新的模式下,客户端的通信方式如下: @@ -58,11 +58,11 @@ win / +------------------+ \ lose ## 客户端接口类型抽象 -我们将客户端接口抽象为下面两大类,这也是对客户端接口的一个规范,对于符合规范的客户端,我们可以自动将其包装为 Leader/Follower 模式 +我们将客户端接口抽象为下面两大类,这也是对客户端接口的一个规范,对于符合规范的客户端,我们可以自动将其包装为 Leader/Follower 模式。 -- 订阅、发布类(subscribe / publish) - - `subscribe(info, listener)` 接口包含两个参数,第一个是订阅的信息,第二个是订阅的回调函数 - - `publish(info)` 接口包含一个参数,就是订阅的信息 +- 订阅、发布类(subscribe / publish): + - `subscribe(info, listener)` 接口包含两个参数,第一个是订阅的信息,第二个是订阅的回调函数。 + - `publish(info)` 接口包含一个参数,就是订阅的信息。 - 调用类 (invoke),支持 callback, promise 和 generator function 三种风格的接口,但是推荐使用 generator function。 客户端示例 @@ -110,8 +110,8 @@ class Client extends Base { ## 异常处理 -- Leader 如果“死掉”会触发新一轮的端口争夺,争夺到端口的那个实例被推选为新的 Leader -- 为保证 Leader 和 Follower 之间的通道健康,需要引入定时心跳检查机制,如果 Follower 在固定时间内没有发送心跳包,那么 Leader 会将 Follower 主动断开,从而触发 Follower 的重新初始化 +- Leader 如果“死掉”会触发新一轮的端口争夺,争夺到端口的那个实例被推选为新的 Leader。 +- 为保证 Leader 和 Follower 之间的通道健康,需要引入定时心跳检查机制,如果 Follower 在固定时间内没有发送心跳包,那么 Leader 会将 Follower 主动断开,从而触发 Follower 的重新初始化。 ## 协议和调用时序 @@ -130,12 +130,12 @@ Leader 和 Follower 通过下面的协议进行数据交换: +-----------------------------------------------------------------------------------------------+ ``` -1. 在通讯端口上 Leader 启动一个 Local Server,所有的 Leader/Follower 通讯都经过 Local Server -1. Follower 连接上 Local Server 后,首先发送一个 register channel 的 packet(引入 channel 的概念是为了区别不同类型的客户端) -2. Local Server 会将 Follower 分配给指定的 Leader(根据客户端类型进行配对) -3. Follower 向 Leader 发送订阅、发布请求, -4. Leader 在订阅数据变更时通过 subscribe result packet 通知 Follower -5. Follower 向 Leader 发送调用请求,Leader 收到后执行相应操作后返回结果 +1. 在通讯端口上 Leader 启动一个 Local Server,所有的 Leader/Follower 通讯都经过 Local Server。 +2. Follower 连接上 Local Server 后,首先发送一个 register channel 的 packet(引入 channel 的概念是为了区别不同类型的客户端)。 +3. Local Server 会将 Follower 分配给指定的 Leader(根据客户端类型进行配对)。 +4. Follower 向 Leader 发送订阅、发布请求。 +5. Leader 在订阅数据变更时通过 subscribe result packet 通知 Follower。 +6. Follower 向 Leader 发送调用请求,Leader 收到后执行相应操作后返回结果。 ```js +----------+ +---------------+ +---------+ @@ -161,7 +161,7 @@ Leader 和 Follower 通过下面的协议进行数据交换: ## 具体的使用方法 -下面我用一个简单的例子,介绍在框架里面如何让一个客户端支持 Leader/Follower 模式 +下面我用一个简单的例子,介绍在框架里面如何让一个客户端支持 Leader/Follower 模式: - 第一步,我们的客户端最好是符合上面提到过的接口约定,例如: @@ -241,7 +241,7 @@ class RegistryClient extends Base { module.exports = RegistryClient; ``` -- 第二步,使用 `agent.cluster` 接口对 RegistryClient 进行封装 +- 第二步,使用 `agent.cluster` 接口对 `RegistryClient` 进行封装: ```js // agent.js @@ -260,7 +260,7 @@ module.exports = agent => { }; ``` -- 第三步,使用 `app.cluster` 接口对 RegistryClient 进行封装 +- 第三步,使用 `app.cluster` 接口对 `RegistryClient` 进行封装: ```js // app.js @@ -294,7 +294,7 @@ module.exports = app => { 是不是很简单? -当然,如果你的客户端不是那么『标准』,那你可能需要用到其他一些 API,比如,你的订阅函数不叫 subscribe,叫 sub +当然,如果你的客户端不是那么『标准』,那你可能需要用到其他一些 API,比如,你的订阅函数不叫 `subscribe` 而是叫 `sub`: ```js class MockClient extends Base { @@ -324,7 +324,7 @@ class MockClient extends Base { } ``` -你需要用 delegate API 手动设置 +你需要通过 `delegate`(API代理)手动设置此委托: ```js // agent.js @@ -358,20 +358,20 @@ module.exports = app => { }; ``` -我们已经理解,通过 cluster-client 可以让我们在不理解多进程模型的情况下开发『纯粹』的 RegistryClient,只负责和服务端进行交互,然后使用 cluster-client 进行简单的 wrap 就可以得到一个支持多进程模型的 ClusterClient。这里的 RegistryClient 实际上是一个专门负责和远程服务通信进行数据通信的 DataClient。 +我们已经理解,通过 `cluster-client` 可以让我们在不理解多进程模型的情况下开发『纯粹』的 `RegistryClient`,只负责和服务端进行交互,然后使用 `cluster-client` 进行简单的封装就可以得到一个支持多进程模型的 `ClusterClient`。这里的 `RegistryClient` 实际上是一个专门负责和远程服务通信进行数据通信的 `DataClient`。 -大家可能已经发现,ClusterClient 同时带来了一些约束,如果想在各进程暴露同样的方法,那么 RegistryClient 上只能支持 sub/pub 模式以及异步的 API 调用。因为在多进程模型中所有的交互都必须经过 socket 通信,势必带来了这一约束。 +大家可能已经发现,`ClusterClient` 同时带来了一些约束,如果想在各进程暴露同样的方法,那么 `RegistryClient` 上只能支持 sub/pub 模式以及异步的 API 调用。因为在多进程模型中所有的交互都必须经过 socket 通信,势必带来了这一约束。 -假设我们要实现一个同步的 get 方法,subscribe 过的数据直接放入内存,使用 get 方法时直接返回。要怎么实现呢?而真实情况可能比这更复杂。 +假设我们要实现一个同步的 get 方法,订阅过的数据直接放入内存,使用 get 方法时直接返回。要怎么实现呢?而真实情况可能比这更复杂。 -在这里,我们引入一个 APIClient 的最佳实践。对于有读取缓存数据等同步 API 需求的模块,在 RegistryClient 基础上再封装一个 APIClient 来实现这些与远程服务端交互无关的 API,暴露给用户使用到的是这个 APIClient 的实例。 +在这里,我们引入一个 `APIClient` 的最佳实践。对于有读取缓存数据等同步 API 需求的模块,在 `RegistryClient` 基础上再封装一个 `APIClient` 来实现这些与远程服务端交互无关的 API,暴露给用户使用到的是这个 `APIClient` 的实例。 在 APIClient 内部实现上: -- 异步数据获取,通过调用基于 ClusterClient 的 RegistryClient 的 API 实现。 -- 同步调用等与服务端无关的接口在 APIClient 上实现。由于 ClusterClient 的 API 已经抹平了多进程差异,所以在开发 APIClient 调用到 RegistryClient 时也无需关心多进程模型。 +- 异步数据获取,通过调用基于 `ClusterClient` 的 `RegistryClient` 的 API 实现。 +- 同步调用等与服务端无关的接口在 `APIClient` 上实现。由于 `ClusterClient` 的 API 已经抹平了多进程差异,所以在开发 `APIClient` 调用到 `RegistryClient` 时也无需关心多进程模型。 -例如在模块的 APIClient 中增加带缓存的 get 同步方法: +例如在模块的 `APIClient` 中增加带缓存的 get 同步方法: ```js // some-client/index.js @@ -489,8 +489,8 @@ class APIClient extends APIClientBase { ``` - RegistryClient - 负责和远端服务通讯,实现数据的存取,只支持异步 API,不关心多进程模型。 -- ClusterClient - 通过 cluster-client 模块进行简单 wrap 得到的 client 实例,负责自动抹平多进程模型的差异。 -- APIClient - 内部调用 ClusterClient 做数据同步,无需关心多进程模型,用户最终使用的模块。API 都通过此处暴露,支持同步和异步。 +- ClusterClient - 通过 `cluster-client` 模块进行简单 wrap 得到的 client 实例,负责自动抹平多进程模型的差异。 +- APIClient - 内部调用 `ClusterClient` 做数据同步,无需关心多进程模型,用户最终使用的模块。API 都通过此处暴露,支持同步和异步。 有兴趣的同学可以看一下[增强多进程研发模式](https://github.com/eggjs/egg/issues/322) 讨论过程。 diff --git a/docs/source/zh-cn/advanced/framework.md b/docs/source/zh-cn/advanced/framework.md index 5b8dd87a94..5f721b3768 100644 --- a/docs/source/zh-cn/advanced/framework.md +++ b/docs/source/zh-cn/advanced/framework.md @@ -375,5 +375,3 @@ describe('/test/index.test.js', () => { }); }); ``` - -[egg-bin]: https://github.com/eggjs/egg-bin diff --git a/docs/source/zh-cn/advanced/loader.md b/docs/source/zh-cn/advanced/loader.md index 4490c548e8..bfc423f6fe 100644 --- a/docs/source/zh-cn/advanced/loader.md +++ b/docs/source/zh-cn/advanced/loader.md @@ -397,7 +397,7 @@ Loader 还提供一些底层的 API,在扩展时可以简化代码,全部 AP ### loadFile -用于加载一个文件,比如加载 `app.js` 就是使用这个方法。 +用于加载一个文件,比如加载 `app/xx.js` 就是使用这个方法。 ```js // app/xx.js @@ -469,7 +469,7 @@ app.loader.loadToContext(servicePaths, 'service', { #### ignore [String] -ignore 可以忽略一些文件,支持 glob,默认为空 +`ignore` 可以忽略一些文件,支持 glob,默认为空 ```js app.loader.loadToApp(directory, 'controller', { diff --git a/docs/source/zh-cn/advanced/plugin.md b/docs/source/zh-cn/advanced/plugin.md index a3fd2dec11..dfd11ade59 100644 --- a/docs/source/zh-cn/advanced/plugin.md +++ b/docs/source/zh-cn/advanced/plugin.md @@ -2,7 +2,7 @@ title: 插件开发 --- -插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。有人可能会问了 +插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。有人可能会问了: - Koa 已经有了中间件的机制,为啥还要插件呢? - 中间件、插件、应用它们之间是什么关系,有什么区别? @@ -427,8 +427,8 @@ class PostController extends Controller { 框架在加载插件的时候,遵循下面的寻址规则: -- 如果配置了 path,直接按照 path 加载 -- 没有 path 根据 package 名去查找,查找的顺序依次是 +- 如果配置了 path,直接按照 path 加载。 +- 没有 path 根据 package 名去查找,查找的顺序依次是: 1. 应用根目录下的 `node_modules` 2. 应用依赖框架路径下的 `node_modules` diff --git a/docs/source/zh-cn/advanced/view-plugin.md b/docs/source/zh-cn/advanced/view-plugin.md index 165c9f90b4..8d548dbbb4 100644 --- a/docs/source/zh-cn/advanced/view-plugin.md +++ b/docs/source/zh-cn/advanced/view-plugin.md @@ -3,7 +3,7 @@ title: View 插件开发 绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户,而框架并不强制使用某种模板引擎,由开发者来自行选型,具体参见[模板渲染](../core/view.md)。 -本文将阐述框架对 View 插件的规范约束, 我们可以依此来封装对应的模板引擎插件。以下以 [egg-view-ejs] 为例 +本文将阐述框架对 View 插件的规范约束, 我们可以依此来封装对应的模板引擎插件。以下以 [egg-view-ejs] 为例。 ## 插件目录结构 @@ -97,15 +97,15 @@ module.exports = class EjsView { `renderString` 方法的三个参数 -- tpl: 模板字符串,没有文件路径 -- locals: 同 `render` -- viewOptions: 同 `render` +- tpl: 模板字符串,没有文件路径。 +- locals: 同 `render`。 +- viewOptions: 同 `render`。 ## 插件配置 -根据上面的命名约定,配置名一般为模板引擎的名字,比如 ejs +根据上面的命名约定,配置名一般为模板引擎的名字,比如 ejs。 -插件的配置主要来自模板引擎的配置,可根据具体情况定义配置项,如 [ejs 的配置](https://github.com/mde/ejs#options) +插件的配置主要来自模板引擎的配置,可根据具体情况定义配置项,如 [ejs 的配置](https://github.com/mde/ejs#options)。 ```js // config/config.default.js @@ -130,7 +130,7 @@ module.exports = { 而这样用起来比较麻烦,而且容易遗忘,所以我们可以封装下: -先提供一个 helper 子类: +- 先提供一个 helper 子类: ```js // {plugin_root}/lib/helper.js @@ -145,7 +145,7 @@ module.exports = app => { }; ``` -在渲染时使用自定义的 helper +- 在渲染时使用自定义的 helper ```js // {plugin_root}/lib/view.js @@ -180,7 +180,7 @@ module.exports = class MyCustomView { } ``` -框架提供了 [app.injectCsrf](../core/security.md#appinjectcsrfstr) 和 [app.injectNonce](../core/security.md#appinjectnoncestr),更多可查看[安全章节](../core/security.md)。 +此外,框架提供了 [app.injectCsrf](../core/security.md#appinjectcsrfstr) 和 [app.injectNonce](../core/security.md#appinjectnoncestr),更多可查看[安全章节](../core/security.md)。 ### 单元测试