Koa MC

12535 ワード

Koaを使用してWeb HTTPサービスを行い、MVCによってプロジェクト構造を組織する.Koa自身はすでにExpress上でかなり簡素化されているため、MVC構造上はKoaのapp上に多くのオブジェクトを注入することは避けられない.requireを使用してファイルをロードすると、プロジェクト規模がますます複雑になると異常に複雑になる.Nodeでは比較的良いIoCが見つかり、Bearcatのみが見つかり、PomeloではすでにBearcatが使用されています.Pomelo自身はasync/awaitをサポートしていないので、いろいろなものがあって、卵が痛いです.今はまずHTTPサーバーを作って、Pomeloの中のいくつかの比較的に良いところを参考にします.
簡単に整理して、Nodeで構築したMVCフレームワークを使用して、Webサービスは最終的に前後の分離を行うため、ここのMVCは実はMとCだけで、主にHTTPインタフェースをして使用します.Vはすでに独立しているので、ここでも放っておきます.WebフレームワークはKoaを使用しており,絶え間ないrequireを避けるためである.MとCとrouterの優雅な組み合わせは、頭の痛い問題です.今は完成していますが、性能はどうなのか分かりません.キーセクションでは、2つの位置で、1つ目はrouterでcontrollerを動的にロードし、2つ目はcontrollerでmodelを動的にロードします.実際には、この2つの点は最終的に動的ロードの問題に帰結します.したがって、個別のローダが抽出される.多方面の資料を参考にして、またPomeloの中でRPCの書き方が優雅であることを発見したので、はっきり言って、比較的手間が省けて、コードを節約します.
さて、本来のニーズを簡単に言えば、本来はPomeloをリアルタイムアプリケーションとして、HTTPインタフェースのサービスを剥離するために、HTTP MVCのサービスを始めたからです.
くだらないことをたくさん言って、まず積み木のコンポーネントを見てみましょう.
に頼る
$ vim web-server/package.json
{
  "name": "web-server",
  "version": "1.0.0",
  "description": "web server",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "web",
    "server"
  ],
  "author": "junchow",
  "license": "ISC",
  "dependencies": {
    "koa": "^2.11.0",
    "koa-router": "^7.4.0",
    "koa-static": "^5.0.0",
    "mysql2": "^2.1.0",
    "sequelize": "^5.21.3"
  }
}

ここでは最も基礎的なkoa、koa-route、sequelize、mysqlを使うだけです.後で必要な再追加.
こうぞう
HTTPサービスのMVC構造
フォルダ
説明
app.js
プロジェクトエントリファイル
config
フォルダを設定します.プロファイルはJSON形式で、developmentとproductionに分けられます.
config/app.json
デフォルトで適用されるプロファイル
route
ルーティングフォルダは、アプリケーションに基づいてルーティングファイルを分割します.
route/web.js
Webアプリケーションのルーティングルールファイル
util
ツールフォルダ
util/loader.js
ファイルローダ
app
MVC組織構造を含むフォルダを適用します.
app/controller
コントローラファイルを適用すると
app/model
データモデルフォルダの適用
app/service
サービス層フォルダ
app/middleware
ミドルウェアファイルの適用
app/view
ビューフォルダの適用
いりぐち
エントリファイルの適用
$ vim app.js
const path = require("path");
const Koa = require("koa");
const koaStatic = require("koa-static");
const KoaRouter = require("koa-router");
const Sequelize = require("sequelize");

//    
const env = process.env.NODE_ENV || "development";
//    
const app = new Koa();
//    
const router = new KoaRouter();

//    
const basePath = __dirname;
const configPath = path.join(basePath, "config");
const staticPath = path.join(basePath, "static");
const routePath = path.join(basePath, "route");
const utilPath = path.join(basePath, "util");

const appPath = path.join(basePath, "app");
const controllerPath = path.join(appPath, "controller");
const modelPath = path.join(appPath, "model");
const viewPath = path.join(appPath, "view");

//    
const config = require(path.join(configPath, "app"))[env];

//        
app.use(koaStatic(staticPath));

//     
const sequelize = new Sequelize(config.sequelize);
//    
sequelize.authenticate()
    .then(_=>console.log("connection has been established successfully"))
    .catch(err=>console.error("unable to connect to the database", err));

//    
const Loader = require("./util/loader");
app.controller = Loader.load(controllerPath);
app.context.model = Loader.load(modelPath, sequelize);

//    
require(path.join(routePath, "web"))(app, router);

//    
app.listen(config.server.port,  config.server.host, _=>{
    console.log("server start", config.server.host, config.server.port);
});

説明する
  • は構成をロードし、現在の環境envパラメータに基づいてデフォルトのプロファイルconfig/app.jsonの構成項目をロードします.
  • パスプランニングは、プロジェクトフォルダの組織構造に基づいてパス変数を定義し、主にpathで完了し、これらのパスをappまたはctxの下に掛けることもできます.
  • データベース接続で、プロジェクトが起動すると直接データベースに接続されます.これは何も言うことはありません.
  • ダイナミックロード、ここではローダLoaderを作成することに重点を置いて、ダイナミックロードファイル、この場所が重要です.

  • 重点的に説明すると、app.jsアプリケーションエントリファイルで最も重要なのはappというオブジェクトと、appオブジェクト上のcontextです.実はappやcontextに多くのものを掛けたくありませんが、コードを書くのに便利で、性能の面では考えていません.
    コンフィギュレーション
    構成の適用
    $ config/app.json
    
    {
      "development":{
        "server":{
          "host":"127.0.0.1",
          "port":3000
        },
        "sequelize":{
          "host":"127.0.0.1",
          "port":3306,
          "username":"root",
          "password":"root",
          "database":"pomelo",
          "dialect":"mysql",
          "dialectOptions":{
            "charset":"utf8mb4"
          },
          "pool":{
            "min":0,
            "max":5,
            "idle":10000,
            "acquire":3000
          },
          "timezone":"+08:00"
        }
      }
    }
    

    動的ロードアプリケーションについては、app.jsエントリファイルでまず現在の環境タイプが定義され、デフォルトではdevelopmentが使用されます.
    //    
    const env = process.env.NODE_ENV || "development";
    

    次に定義された環境タイプに基づいてrequireを行います
    const config = require(path.join(configPath, "app"))[env];
    

    後でコンポーネントを追加する場合は、app.jsonに直接構成項目を追加し、app.jsでconfigを使用します.
    データベース#データベース#
    データベースでは卵が痛くてsequelizeというORMを使っています.なぜORMを使うのかというと、ORMはパフォーマンスを消費しますが、手間を省くために、次は異なるデータベースを互換化するためです.自分でmysqlやmogooseでやってもいいですが、ここは主にノードのORMを勉強するためです.
    app.jsでは、データベース構成と接続としてsequelizeオブジェクトを使用するだけで、modelを作成するときに必要です.各modelでconfigを先にconnectすることはできません.これにより冗長コードが生成されます.初期化接続を個別にファイルにすると、各モデルにはrequireが山積みになります.本当にrequireが好きではありません.プロジェクトはますます複雑になるだけです.requireになると、後で再構築するのは幽霊に会うようになります.問題が来ました.sequelizeを各モデルに渡すにはどうすればいいですか?ここでまず考えてからにしよう.
    //     
    const sequelize = new Sequelize(config.sequelize);
    //    
    sequelize.authenticate()
        .then(_=>console.log("connection has been established successfully"))
        .catch(err=>console.error("unable to connect to the database", err));
    

    ルート
    先にルートを言いますが、ルートはMVCの第一関門です.
    //    
    require(path.join(routePath, "web"))(app, router);
    

    ここではroute/web.jsがデフォルトでロードされ、web.jsはwebアプリケーションを使用するすべてのルーティングルールを表し、appとrouterが転送されます.この中でもこの書き方はよくありませんが、appやrouterを渡さないと、web.jsがrouterを返すだけで、いくつかの問題があります.Web.jsのルーティングを見てみましょう.
    $ vim route/web.js
    
    module.exports = (app, router) => {
        //    
        router.get("/", app.controller.index.index);
        router.get("/test", app.controller.index.test);
        //    
        app.use(router.routes()).use(router.allowedMethods());
    };
    
    app.controller.index.indexという書き方を見て、個人的には好きです.最初のルーティングファイルにはコントロールを導入する必要はありません.導入すれば、後で頭が痛いものがたくさんあります.次に、この書き方は直感的で、フォルダの構造に直接対応し、最終的に具体的な方法に達します.しかし問題はappもここまでです.routerからcontroller以降、requireしないとappも使えなくなります.それがどのように書かれているのか、ここではやはりローダが果たす役割です.これまでLoaderローダでコントロールをappに掛けていたからです.
    //    
    const Loader = require(path.join(utilPath, "loader"));
    app.controller = Loader.load(controllerPath);
    

    ここではやはりloaderが重要な役割を果たしており、いくつかの案を参考にして、pomeloでは使い方が多いので、最終的にはこれらの方法がいいと思います.後で話しましょう.
    せいぎょそうち
    ルーティングがコントローラに達した以上、コントローラがルーティングを受け入れると、2つのパラメータctxコンテキストとnextコールバック関数があります.koaはasync/awaitをサポートしているので、この点は非常に良いです.以前のコールバック地獄のコードロジックは、苦痛に見えます.asyncのwaterfallもありますが、正直見ても違和感があります.pomeloでは、rpcがasync/awaitをサポートしていないため、私もasyncを使わなければなりません.2つの方法で比較しなければなりません.本当に感じが違います.
    $ vim app/controller/index.js
    
    exports.index = async (ctx, next)=>{
      const where = {aid:900005};
      const users = await ctx.model.user.findAll({where});
      console.log(users);
    
      ctx.body = "homepage";
    };
    
    exports.test = async (ctx, next)=>{
      console.log("index test");
    };
    

    特定のコントローラメソッドにルーティングした後,コントローラはモデルを呼び出してデータを取得することは言うまでもない.ここも長い間考えていた位置です.またrequireが山積みになったら、シイタケがつらいからです.ここでは、app/modelフォルダの下にあるuserモデルを直接呼び出すことに重点を置いています.userモデルはsequelizerを使用して作成され、sequelizerのmodelではfindAllの方法が提供されています.最初の考えはrequireを適用してモデルをロードしない方法で、後でできないことに気づいた.次にctxで何をするか考えて、いくつかのdemoを試してみました.コードが本当に醜いことに気づいた.今やっともっと直感的になれるようになりました.ここはctxですが、appがcontrollerに届いたので、もう直接使うことはできません.だからここではctxで仕事をするしかありません.app.jsエントリファイルに戻るには、カスタムLoaderローダを使用してmodelを動的にロードし、sequelizeオブジェクトを貫通することが重要です.
    app.context.model = Loader.load(modelPath, sequelize);
    

    書き方の上で推定するのは少し奇妙で、熟知していない後に推定するのはとても良くて、requireがなくて、感じは本当にとても良いです.
    モデル#モデル#
    モデルはここでも言いすぎて、sequelizerを使ったので、すべての仕事はそれが完成して、自分も何もしていません.以前ormで自分でmysqlでmodelのベースクラスを作って、sqlをつづって、使ってもいいです.自分でパッケージして、使いやすいです.またormが来て、少なくともデータベースを交換すると、ここはずっと強くなります.sequelizerも見つけたばかりで、ゆっくりと勉強し始めました.まずNodeのORMを勉強してからにしよう.
    const Sequelize = require("sequelize");
    module.exports = sequelize=>{
       const model = sequelize.define("users",{
          aid:{type:Sequelize.BIGINT(11), allowNull:false},
          unick:{type:Sequelize.STRING(20), allowNull:false},
          uface:{type:Sequelize.STRING(255), allowNull:false},
       });
       model.removeAttribute("id");
       model.removeAttribute("createdAt");
       model.removeAttribute("updatedAt");
       return model;
    };
    

    ここでは、受信したsequelizerを指定したデータベース・テーブル名を定義し、リレーショナル・オブジェクトのマッピングを簡単に行います.コードは優雅ではなく、しばらくsequelizerにはあまり使われていません.まずそうします.まず流れを通す.
    ローダ
    ここで重要なポイントは、以前requireを使用していたときに、1つのローダを個別にカプセル化するのにどのような役割があるかが見つからなかったローダにあります.他のフレームワークではそうしますが、cocosのようなものを書いた後、ローダが重要であることに気づきました.こちらも簡単に使っただけです.一部のプロジェクトはfsに直接行ってファイルやフォルダを読み、すべてrequireします.ネットワークアプリケーションでioの読み書きは比較的骨が折れる操作であり、ローダはこれらのことをします.ここのローダもファイルを読み取りに行きます.ただ、ファイル全体を読み取るのではなく、動的に読み取るだけです.つまり、どちらを読むか、またここのローダはconst users = await ctx.model.user.findAll({where});のような書き方でモジュール内の方法を読み取るので、非常に友好的です.
    $ vim util/loader.js
    
    const path = require('path');
    const fs = require('fs');
    
    class Loader {
        constructor () {}
        static load(filepath, options=null){
            const data = [filepath];
            const handler = {
                construct(target, args){
                  return false;
                },
                get(target, key, receiver){
                    if(key && Object.prototype.toString.call(key)==="[object String]"){
                        if(data.indexOf(key) === -1){
                            data.push(key);
                        }
                    }
                    const file = path.resolve(__dirname, path.relative(__dirname, data.join("/")));
                    //console.log(data, key, data.indexOf(key), file);
                    if(Loader.isDirectory(file)){
                        return new Proxy({path: data}, handler);
                    }else if(Loader.isFile(file)){
                        if(options){
                            return require(file)(options);
                        }else{
                            return require(file);
                        }
                    }
                }
            };
            return new Proxy({path:data}, handler);
        }
        static isDirectory (target) {
            if(!fs.existsSync(target)){
                return false;
            }
            let stat;
            try{
                stat = fs.statSync(target);
            }catch(e){
                console.error(e);
            }
            if(stat && stat.isDirectory()) {
                return true
            }
            return false;
        }
    
        static isFile (target) {
            target = target + ".js";
            if(!fs.existsSync(target)){
                return false;
            }
            let stat;
            try{
                stat = fs.statSync(target);
            }catch(e){
                console.error(e);
            }
            if(stat && stat.isFile()) {
                return true;
            }
            return false;
        }
    }
    module.exports = Loader;
    

    LoaderではNodeのProxyコンポーネントを使うのがポイントで、proxyという単語に変な感じがします.Proxyのドキュメントを読んでいても、なんとなくわからない状態.ゆっくりして、どうせproxyという単語に対して、いつも雲の中の霧の中の感じがして、はっきり言っても分からないし、分からないと言っても分からない.やはりよく通じていません.1章を変えて単独で深く研究する.
    はい、そのために、基本的な棚があります.少なくとも個人のニーズを満たしています.パフォーマンスと問題については、使用中に最適化されています.やりながら勉強して考える.他のモジュールの追加は簡単ですが、ここでもあまり言いません.アップグレードを最適化してからゆっくり補充します.
    実は、ずっとIoCの容器を持ってこの対象を管理したいと思っていましたが、ずっと発見していなかったほうがいいです.良い車輪が見つからないので、勉強して参考にすることができます.pomeloではbearcatを使っていますが、正直bearcatの書き方にもあまり慣れていません.やはり自分で深くソースを勉強しなければなりません.