私はORMから吐き出された多くの憎しみを読んでおり、物事を行うためのより良い方法があるかどうかを見つけることに本当に興味があります。
残念ながら、さまざまなORMを使用したアプリのモデリングと構築に関する書籍、ブログ投稿、記事はたくさんありますが、非ORMアプローチにはありません。
ORMなしで構築された中型/大型のオープンソースWebアプリケーションはありますか?実際のソースコードを読むのに勝るものはありません。
あるいは、ここに私が調べようとしているものがあります:
ORMへの私の露出はRails/ActiveRecordを介してのみであり、Railsが処理するすべてのボイラープレートを複製せずに、DBを使用したアプリケーションを作成している自分を視覚化することはできません。 hibernateとnhibernateは別の獣であり、アクティブなレコードは問題ありません。
Stack Overflowはmicro-ormを使用します。 Hibernateとは異なり、SQLクエリに関する薄い単板にすぎません。
あなたの質問への回答:
SQLクエリを記述する。
コードは1行から約40行までさまざまです。既製のDTOを用意すると役立ちます。
DTOは同じように見えます。 1つではなく2つのクエリを実行し、2つ目のクエリを遅延ロードする場合があります。
JavaScriptでは、JSONは直接JavaScriptオブジェクトに変換されます。他の言語については、Newtonsoft.Jsonなどを使用して、JSONを選択した言語のオブジェクトに変換できます。
ここにいくつかのサンプルコードがあります:
var result = database.ExecuteSqlQuery<Customer>(
"SELECT * FROM Customer WHERE CustomerID = {0}", customerID).FirstOrDefault();
ここで、ExecuteSqlQuery
はCustomer
オブジェクトのリストを返します。 Customer
は、クエリの一部またはすべての列に対応するパブリックメンバーを含むクラスです。
Dapperは、このようなクエリを実行できます。特定のタイプのオブジェクトのコレクション、またはdynamic
オブジェクトのコレクションを返します。
ElixirのEctoをチェックしてください。これは、(主にリレーショナル)データベースとやり取りしてクエリを作成するためのドメイン固有言語(DSL)です。これは、サーバー側のMVCパターンを実装するPhoenixフレームワークに適しています。
あなたの質問に答えるには:
たとえば、ユーザーモデルは次のようになります。
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
def show(conn, %{"id" => id}) do
user = Repo.get!(User, id)
render(conn, "show.json", user: user)
end
上記では、MyApp.User
はUser
としてエイリアスされています。 Repo
またはリポジトリはEctoに付属しており、データベースアダプター(Postgresのpostgrexなど)とともに、基礎となるデータストアにマップされます。
# 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
関数に必要な結果を渡します。
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
チェック制約がある場合、それらも上記のパイプラインの一部であり、移行ファイルにも含まれています。
ORMを使用していないアプリケーションに役立つさまざまなデザインパターンがあります。 Martin Fowlerの著書Patterns of Enterprise Application Architectureを読むことをお勧めします。これには、いくつかの便利なものが含まれていますが、ここではいくつかの短い要約を示します。
ORMを使用せずに、使い慣れたActive Recordパターンを実装することも可能です。各クラスのfindメソッドとsaveメソッドを手動で実装するだけです。ただし、これはおそらく、ORMを使用しないという目的に反します。これは、一般に、データベースとのやり取りをより柔軟に行えるようにするためです。
私は小さなサッカートーナメントアプリケーションを持っています: 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