web-dev-qa-db-ja.com

ORMなしで構築された中規模または大規模のWebアプリの例?

私はORMから吐き出された多くの憎しみを読んでおり、物事を行うためのより良い方法があるかどうかを見つけることに本当に興味があります。

残念ながら、さまざまなORMを使用したアプリのモデリングと構築に関する書籍、ブログ投稿、記事はたくさんありますが、非ORMアプローチにはありません。

ORMなしで構築された中型/大型のオープンソースWebアプリケーションはありますか?実際のソースコードを読むのに勝るものはありません。

あるいは、ここに私が調べようとしているものがあります:

  1. oRMを使用していないからといって、テーブル間のさまざまな関連付けが消えることはありません。アプリでそれらをどのようにモデル化しますか?
  2. PKを指定して行をロードするような単純なものの場合、どれだけのボイラープレートを記述する必要がありますか?この痛みを和らげるライブラリはありますか?
  3. 1:多くの関連付けをロードするような単純なものの場合、どれだけのボイラープレートが必要ですか?
  4. WebアプリでJSON APIを公開しているとき、コードはどのように見えますか? JSON APIには本質的に関連があります(つまり、ユーザーJSONには投稿が含まれ、コメントが含まれます)。これはORMの機能と実質的にどのように異なりますか?
  5. さまざまなビジネスロジックを実装する低レベルAPIはどのように見えますか?オブジェクトでない場合、このAPIはどのような引数を取りますか?それは何を返しますか?

ORMへの私の露出はRails/ActiveRecordを介してのみであり、Railsが処理するすべてのボイラープレートを複製せずに、DBを使用したアプリケーションを作成している自分を視覚化することはできません。 hibernateとnhibernateは別の獣であり、アクティブなレコードは問題ありません。

6
Saurabh Nanda

Stack Overflowはmicro-ormを使用します。 Hibernateとは異なり、SQLクエリに関する薄い単板にすぎません。

あなたの質問への回答:

  1. SQLクエリを記述する。

  2. コードは1行から約40行までさまざまです。既製のDTOを用意すると役立ちます。

  3. DTOは同じように見えます。 1つではなく2つのクエリを実行し、2つ目のクエリを遅延ロードする場合があります。

  4. JavaScriptでは、JSONは直接JavaScriptオブジェクトに変換されます。他の言語については、Newtonsoft.Jsonなどを使用して、JSONを選択した言語のオブジェクトに変換できます。

  5. ここにいくつかのサンプルコードがあります:

    var result = database.ExecuteSqlQuery<Customer>(
        "SELECT * FROM Customer WHERE CustomerID = {0}", customerID).FirstOrDefault();
    

ここで、ExecuteSqlQueryCustomerオブジェクトのリストを返します。 Customerは、クエリの一部またはすべての列に対応するパブリックメンバーを含むクラスです。

Dapperは、このようなクエリを実行できます。特定のタイプのオブジェクトのコレクション、またはdynamicオブジェクトのコレクションを返します。

7
Robert Harvey

ElixirのEctoをチェックしてください。これは、(主にリレーショナル)データベースとやり取りしてクエリを作成するためのドメイン固有言語(DSL)です。これは、サーバー側のMVCパターンを実装するPhoenixフレームワークに適しています。

あなたの質問に答えるには:

  1. &3.関係のモデリングはモデルで行われます。 Elixirは関数型言語であり、クラスはありません。代わりに、Ecto DSLを使用してテーブルスキーマを宣言します。これにより、基本的にElixir言語が拡張され、短くて表現力豊かなデータベース宣言が提供されます。データベース内のすべてのテーブルについて、モデルモジュールと対応する移行ファイルがあります(各環境(dev、test、prodなど)でdb移行を1回実行するため)。

たとえば、ユーザーモデルは次のようになります。


    defmodule MyApp.User do
    . . .
      schema "users" do
        field :name, :string
        field :email, :string
        field :bio, :string
        field :number_of_pets, :integer

        has_many :videos, MyApp.Video
        timestamps()
      end
    . . .
    end

次のようなVideoモジュールを参照します。


    defmodule MyApp.Video do
    . . .
      schema "videos" do
        field :name, :string
        field :approved_at, Ecto.DateTime
        field :description, :string
        field :likes, :integer
        field :views, :integer
        belongs_to :user, MyApp.User

        timestamps()
      end
    . . .
    end
  1. これは、UserControllerモジュールに次のようなものを追加することで実行できます。

    def show(conn, %{"id" => id}) do
      user = Repo.get!(User, id)   
      render(conn, "show.json", user: user)
    end

上記では、MyApp.UserUserとしてエイリアスされています。 RepoまたはリポジトリはEctoに付属しており、データベースアダプター(Postgresのpostgrexなど)とともに、基礎となるデータストアにマップされます。

  1. 次のスニペットを検討してください。

    # Create a query
    query = from p in Post,
              join: c in Comment, where: c.post_id == p.id

    # Extend the query
    query = from [p, c] in query,
              select: {p.title, c.body}

基本的に、上記を使用してデータベースにクエリを実行し、JSONをレンダリングするrender関数に必要な結果を渡します。

  1. Elixirには、データベースにコミットする準備ができる前に着信データ(構造体)が受ける一連の変換(またはパイプライン)であるチェンジセットがあります。入ってくる構造体を処理している間、チェンジセットはフィルタリング、キャスト、検証、制約の定義を可能にします。たとえば、チェンジセット関数は次のようになります(これはMyApp.Userモジュール(モデル)の一部です)。

    def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, [:name, :email, :bio, :number_of_pets])
        |> validate_required([:name, :email, :bio, :number_of_pets])
        |> validate_length(:bio, min: 2)
        |> validate_length(:bio, max: 140)
        |> validate_format(:email, ~r/@/)
    end

チェック制約がある場合、それらも上記のパイプラインの一部であり、移行ファイルにも含まれています。

3

ORMを使用していないアプリケーションに役立つさまざまなデザインパターンがあります。 Martin Fowlerの著書Patterns of Enterprise Application Architectureを読むことをお勧めします。これには、いくつかの便利なものが含まれていますが、ここではいくつかの短い要約を示します。

  • Table Data Gateway -単一のテーブルを操作するための操作を持つクラスを作成します
  • Row Data Gateway -データベーステーブルの単一の行を表すクラスを作成し、ドメインモデルオブジェクトと対話する操作を提供します
  • Query Object -SQLクエリを表すクラス。クエリに制限を追加したり、列を選択したりするためのプロパティがあります。
  • トランザクションスクリプト -単一の操作中にデータベースで行われたすべての作業をカプセル化するオブジェクト

ORMを使用せずに、使い慣れたActive Recordパターンを実装することも可能です。各クラスのfindメソッドとsaveメソッドを手動で実装するだけです。ただし、これはおそらく、ORMを使用しないという目的に反します。これは、一般に、データベースとのやり取りをより柔軟に行えるようにするためです。

3
Jules

私は小さなサッカートーナメントアプリケーションを持っています: http://ng2016.zayso.org/schedule/game

スケジュールドメイン内で、Gameは、GameTeamsとGameOfficialsへの関連付けを持つar(集合ルート)です。

ゲームFinderは次のようになります。

public function findGames(array $criteria)
{
    $conn = $this->gameConn;

    // Find unique game ids (might require several queries)
    $gameIds = $this->findGameIds($conn,$criteria);

    // Load the games
    $games = $this->findGamesForIds($conn,$gameIds);

    // Load the teams
    $wantTeams = isset($criteria['wantTeams']) ? $criteria['wantTeams'] : true;
    if ($wantTeams) {
        $games = $this->joinTeamsToGames($conn, $games);
    }

    // Load the officials
    $wantOfficials = isset($criteria['wantOfficials']) ? $criteria['wantOfficials'] : false;
    if ($wantOfficials) {
        $games = $this->joinOfficialsToGames($conn,$games);
    }

    // Convert to objects
    $gameObjects = [];
    foreach($games as $game) {
        $gameObjects[] = Game::createFromArray($game);
    }
    // Done
    return $gameObjects;
}

したがって、キーと値の配列を使用してたくさんのゲームを見つけ、必要に応じてチームと役員を追加してから、Gameオブジェクトに変換します。

簡単なクエリは、データベースからゲームをロードします。

private function findGamesForIds(Connection $conn, $gameIds)
{
    if (!count($gameIds)) {
        return [];
    }
    $sql = 'SELECT * FROM games WHERE gameId IN (?) ORDER BY gameNumber';
    $stmt = $conn->executeQuery($sql,[$gameIds],[Connection::PARAM_STR_ARRAY]);
    $games = [];
    while($game = $stmt->fetch()) {
        $game['teams']     = [];
        $game['officials'] = [];
        $game['gameNumber'] = (integer)$game['gameNumber'];
        $games[$game['gameId']] = $game;
    }
    return $games;
}

簡単です。テーブルの列名をオブジェクトプロパティ名に変換しようとしないことに注意してください。テーブルにあるものは何でも使用します。

チームの追加はもう少し手間がかかります:

private function joinTeamsToGames(Connection $conn, array $games)
{
    if (!count($games)) {
        return [];
    }
    $sql = <<<EOD
SELECT 
  gameTeam.gameTeamId,gameTeam.gameId,gameTeam.gameNumber,
  gameTeam.slot,gameTeam.misconduct,
  poolTeam.regTeamId,poolTeam.regTeamName,
  poolTeam.division,poolTeam.poolTeamKey

FROM      gameTeams AS gameTeam
LEFT JOIN poolTeams AS poolTeam ON poolTeam.poolTeamId = gameTeam.poolTeamId
WHERE gameTeam.gameId IN (?)
ORDER BY gameNumber,slot
EOD;
    $stmt = $conn->executeQuery($sql,[array_keys($games)],[Connection::PARAM_STR_ARRAY]);
    while($gameTeam = $stmt->fetch()) {
        $gameId = $gameTeam['gameId'];
        $games[$gameId]['teams'][$gameTeam['slot']] = $gameTeam;
    }
    return $games;
}

このコンテキストでは、GameTeamは実際には2つの異なるテーブルに格納されていることに注意してください。これは、私が以前使用していたORMで問題があったようなものです。関連するPoolTeamを使用してGameTeamを簡単に取得できましたが、2つをマージする簡単な方法はありませんでした。

GameTeam.misconductはシリアル化された値オブジェクトであることも指摘しておきます。

同様のファインダーを持つ他のいくつかのarがあります。全体的に、このアプローチは、ORMに基づく以前のアプローチよりも維持が容易だと思います。 ORMはボイラープレートのクエリ作業の一部を実行しましたが、カスタマイズが難しいことがわかりました。

ここに完全なソースコード(いぼおよびすべて): https://github.com/cerad/ng2016

0
Cerad