Dependency Injection

「DI パターン」と これを利用した「DI コンテナ」ってのがある

「DI コンテナ」使いたいけど「DI パターン」って何?状態 > 荒いけどまとめる

ここにあることが全部ではないので、正確なことは最下記リンクへ

DI パターンとは何か

インスタンス変数にオブジェクトを割当てること」

「或るクラスに依存するオブジェクトの作成を、他の誰かの責務にすること」

James Shore 曰く

「大げさな言い回しだ(良い意味でと付け加えておく)」

まず DI パターンが出来るまでの実情

概要

  • 一般的に或るクラスは 多数の他クラスオブジェクト (以下 "dependencies")
    を属性として持っている
  • "dependencies" は要求/仕様変更/テスト実行時に
    メンテナンス、テストによってはスタブ/モックの用意が必要
    • ...この「メンテナンス、スタブ/モック用意」
      やり易い方法がないものか?

詳細(例を交え)

  • 下サンプルの SmileCar について、 Wheel/Engine の "dependencies" は vendorABC である
  • もし、"dependencies" が vendorABC ではなく vendorXYZ になった場合
  • "dependencies" をそれぞれ vendorABCWheel(), vendorABCEngine() を変更する
    • ... が、これって本当に良いやり方?
      • メンテは面倒(影響範囲調査など、巨大クラスになれば尚更)
      • 責任範囲という面でも SmileCar(車自身) が
        "dependencies"(Wheel(タイヤ) や Engine(エンジン)) の「生成」にまで
        責務を持つべき?

(before)

(Whee/Engine がそれぞれ 抽象クラスを具象化したクラスであったり
interface を implements している前提をかなりすっ飛ばしているのは意図的)

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel     = new vendorABCWheel();  // 仕様変更でメンテが必要になりそう
  private Engine engine   = new vendorABCEngine(); // 同上

  public Car() {}

  public showWheelNameAndEngineName() {
    try {
      // テストの時はどこの DB 見にいく?
      // getConnection() 内で 本番の時は... テストの時は...
      // って 分岐してないですよね?
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

(after)

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel   = new vendorXYZheel();   // vendorABC > vendorXYZ
  private Engine engine = new vendorXYZEngine(); // 同上

  public Car() {
    db = new DatabaseBase();
  }

  public showWheelNameAndEngineName() {
    try {
      // テストの時はどこの DB 見にいく?
      // getConnection() 内で 本番の時は... テストの時は...
      // って 分岐してないですよね?
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

DI パターンの登場

やることは単純に、冒頭通り

インスタンス変数にオブジェクトを割当てること

上の SmileCar (仕様変更後) を例にする

インスタンス変数にオブジェクトを割当てる

まずは Wheel/Engine オブジェクトをインスタンス変数に割当てる

これで、Wheel/Engine の仕様変更があっても「SmileCar クラス自身」は手をつけなくていい

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel;
  private Engine engine;

  public Car(Wheel wheel, Engine engine) {
    db = new DatabaseBase();
    this.wheel  = wheel;
    this.engine = engine;
  }

  public showWheelNameAndEngineName() {
    try {
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

次に db オブジェクトをインスタンス変数に割当てる

クラス設計がいけてないので「え?」となるが、あくまで一例

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel;
  private Engine engine;

  public Car(DatabaseUtil db, Wheel wheel, Engine engine) {
    this.db     = db;
    this.wheel  = wheel;
    this.engine = engine;
  }

  public showWheelNameAndEngineName() {
    try {
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

DI 自体は終わったが、データベースモック化はどんな風に?

概略コードは以下

  • SmileCar は何もしなくて良い
  • 本番DB に繋ぐかモックしたものを作るかは MockDatabase ないの処理で決める
public MockDatabase extends DatabaseUtil {
  // モック用のフィールド、メソッド用意
}

public class SmilCarTest {
  public void TestShowWheelNameAndEngineName() {
    MockDatabase mockDatabase = new MockDatabase()
    SmileCar smileCar = new SmileCar(
      mockDatabase, new vendorXYZWheel(), new venXYZEngine()
    );
    smileCar.showWheelNameAndEngineName();
    smileCar.sometest();
  }
}

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel;
  private Engine engine;

  public Car(DatabaseUtil db, Wheel wheel, Engine engine) {
    this.db     = db;
    this.wheel  = wheel;
    this.engine = engine;
  }

  public showWheelNameAndEngineName() {
    try {
      // (特に指定がなければ) 本番用の DB を使う
      // テスト用の DB(モック) を使う > SmileCarTest#TestShowWheelNameAndEngineName
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

「依存性の注入」について思うこと(感想レベル)

DI[Dependency Injection] が 「依存性の注入」と訳されている

少しでも英語リソースやそこにある議論を眺めたら "dependency"

が「依存」そのものじゃなくて

「オブジェクト」やこれに近しい「モノそれ自身」だと気づくはず

引用参考リンク

http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html

http://stackoverflow.com/questions/130794/what-is-dependency-injection?rq=1

転載(他):Why Use Stored Procedure

  • Pro
    • added layer of security that can be placed on the database from
    • the data functionality making it easier to manage, document, and maintain
    • improved performance
    • multiple client applications to have consistent database routines
  • Con
    • to many calls to sql-server
    • hard to test
One of the most beneficial reasons to use stored procedures is
the added layer of security that can be placed on the database from the calling application.
If the user account created for the application or web site is
configured with EXECUTE permissions
only then the underlying tables cannot be accessed directly by the user account.
This helps prevent hacking directly into the database tables.
The risk of a hacker using the user account to run a stored procedure
that has been written by you is far safer
than having the user account have full insert, update and delete authority
on the tables directly.

Another advantage to using stored procedures, especially in medium to large scale web sites or applications,
is the data functionality is separated from the application making it easier to manage, document, and maintain.
For example, if an application updates the customer table in ten different places,
there can be a single stored procedure and a standard procedure call from the application for this functionality.
If a change needs to be made to the way a customer record is managed,
then the SQL statements only need to be changed in one place, in the database layer.
In most cases, the application is not affected unless the procedure call requires modification.
Changing the procedure call is also easier, because a standard call is already in place.
Managing the data in the data layer avoids having to keep track of embedded SQL calls
that may be different in each place, whenever a change is required.

Stored procedures provide improved performance because fewer calls need to be sent to the database.
For example, if a stored procedure has four SQL statements in the code,
then there only needs to be a single call to the database
instead of four calls for each individual SQL statement.
Of course there is always a tradeoff.
There is an increased workload on the server side that needs to be taken into account.

Another advantage to using stored procedures allows for multiple client applications written in any language
and running on any platform to have consistent database routines.
Each application uses the same procedures and simply has to embed a standard procedure
call for the language in the calling program.

mysqlstoredprocedure.com

blog.eweibel.net

自己結合はいつ使うか

f:id:mat5ukawa:20160307222909j:plain

自テーブル各レコードの特性を

単純な射影や選択では比較できない場合に使う

ここでは CASE, 相関サブクエリ, 内部/外部結合などに言及しないとする

(Web 記事や書籍を眺めてみたのだが、なかなか扱いがよくわからん構文

勉強しきれてないが、これ使うのであれば

OLAP 関数使ったりテーブル設計見直したほうが、良いのでは)

テーブル論理要件

  • 会社員テーブル(employees) が以下の属性を持っている
    • 会社員ID(employeeid)
    • 上司ID(bossid)
    • 会社員名
  • 会社員テーブルは以下の特性を持つ
    • その会社員が誰を上司とするかを判断する為に
      • 上司ID が 会社員ID を参照
      • ただし、上司を持たない会社員は bossid が 0 である
    • bossid = 0 のデータは内部で事前に用意していおく

テーブル物理要件

CREATE TABLE employees (
  employeeid INTEGER
    NOT NULL
  bossid INTEGER
    NOT NULL
    DEFAULT 0, /* 0 means "I don't have boss" */
  name VARCHAR(64)
    NOT NULL,
  PRIMARY KEY (employeeid),
  FOREIGN KEY bossid REFERENCES(employees),
  CONSTRAINT employees_identifier_validation
    CHECK((employeeid >= 0) AND (bossid >= 0))
);
SELECT
  *
FROM
  employees;
employeeid | bossid |        name         
------------+--------+---------------------
         0 |      0 | you have not a boss
         1 |      0 | Sakano
         2 |      1 | Yoneda
         3 |      1 | Umino
         4 |      3 | Saejima
         5 |      4 | Kobayashi

要望

各社員の名前, その社員の上司名

を表示する SQL を書いて欲しい

内部でダミーの社員ってのを用意しているらしいけど

そのデータは表示しないように

実行 SQL

SELECT
  EMP_MAIN.name AS "When employee is...",
  EMP_SUB.name  AS "Then Boss is..."
FROM
  employees AS EMP_MAIN, employees AS EMP_SUB
WHERE
  EMP_MAIN.employeeid <> 0
  AND
  EMP_MAIN.bossid = EMP_SUB.employeeid;
When employee is... |   Then Boss is...   
---------------------+---------------------
Sakano              | you have not a boss
Yoneda              | Sakano
Umino               | Sakano
Saejima             | Umino
Kobayashi           | Saejima

蛇足

SELECT
  EMP_MAIN.bossid,
  EMP_MAIN.name AS "When employee is...",
  EMP_SUB.employeeid,
  EMP_SUB.name  AS "Then Boss is..."
FROM
  employees AS EMP_MAIN, employees AS EMP_SUB
EMP_MAIN.bossid | When employee is... | EMP_SUB.employeeid |   Then Boss is...   
--------+---------------------+------------+---------------------
     0 | you have not a boss |          0 | you have not a boss
     0 | you have not a boss |          1 | Sakano
     0 | you have not a boss |          2 | Yoneda
     0 | you have not a boss |          3 | Umino
     0 | you have not a boss |          4 | Saejima
     0 | you have not a boss |          5 | Kobayashi
     0 | Sakano              |          0 | you have not a boss
     0 | Sakano              |          1 | Sakano
     0 | Sakano              |          2 | Yoneda
     0 | Sakano              |          3 | Umino
     0 | Sakano              |          4 | Saejima
     0 | Sakano              |          5 | Kobayashi
     1 | Yoneda              |          0 | you have not a boss
     1 | Yoneda              |          1 | Sakano
     1 | Yoneda              |          2 | Yoneda
     1 | Yoneda              |          3 | Umino
     1 | Yoneda              |          4 | Saejima
     1 | Yoneda              |          5 | Kobayashi
     1 | Umino               |          0 | you have not a boss
     1 | Umino               |          1 | Sakano
     1 | Umino               |          2 | Yoneda
     1 | Umino               |          3 | Umino
     1 | Umino               |          4 | Saejima
     1 | Umino               |          5 | Kobayashi
     3 | Saejima             |          0 | you have not a boss
     3 | Saejima             |          1 | Sakano
     3 | Saejima             |          2 | Yoneda
     3 | Saejima             |          3 | Umino
     3 | Saejima             |          4 | Saejima
     3 | Saejima             |          5 | Kobayashi
     4 | Kobayashi           |          0 | you have not a boss
     4 | Kobayashi           |          1 | Sakano
     4 | Kobayashi           |          2 | Yoneda
     4 | Kobayashi           |          3 | Umino
     4 | Kobayashi           |          4 | Saejima
     4 | Kobayashi           |          5 | Kobayashi
(36 rows)
WHERE
  EMP_MAIN.employeeid <> 0
  AND
  EMP_MAIN.bossid = EMP_SUB.employeeid;
When employee is... |   Then Boss is...   
---------------------+---------------------
Sakano              | you have not a boss
Yoneda              | Sakano
Umino               | Sakano
Saejima             | Umino
Kobayashi           | Saejima

相関サブクエリはいつ(一番)使われるか

f:id:mat5ukawa:20160307004920j:plain

(一番)使われるのは「レコードの存在チェック」

Web 記事を色々眺めて、私なりの答え

(業務システムの SQL を見ていると「このオペレーションは、いつ、何のために使うのか」

を見失うので一回整理したい)

  • 利用者が存在し、各属性を持つ
    • 利用者 ID
    • 年齢(0 以上 100 以下)
    • 名前(255 文字以内の可変長)
CREATE TABLE users (
  userid INTEGER
    NOT NULL,
  age INTEGER
    NOT NULL
    CHECK (age  BETWEEN 0 AND 100),
  name VARCHAR(255)
    NOT NULL,
  PRIMARY KEY(userid)
);
 userid | age |   name    
--------+-----+-----------
      1 |  26 | Akayama
      2 |  30 | Urano
      3 |  25 | Kitano
      4 |  30 | Mashita
      5 |  33 | Okajima
      6 |  26 | Iseno
      7 |  22 | Kashima
      8 |  28 | Takahashi

集計関数を使わない場合

同年齢のユーザー一覧を表示する

SELECT
  *
FROM
  users AS USR
WHERE
  EXISTS (
    SELECT
      1
    FROM
      users AS USRSUB
    WHERE
      USR.userid <> USRSUB.userid
      AND
      USR.age = USRSUB.age
  );
userid | age |  name   
--------+-----+---------
     6 |  26 | Iseno
     1 |  26 | Akayama
     4 |  30 | Mashita
     2 |  30 | Urano

集計関数を使う場合

総ユーザーにおける平均年齢以上の

ユーザー一覧を表示する

SELECT
  AVG(age)
FROM
  users;
avg         
----
27.5
SELECT
  *
FROM
  users AS USR
WHERE
  age >= (
    SELECT
      AVG(USRSUB.age)
    FROM
      users AS USRSUB
  );
userid | age |   name    
--------+-----+-----------
     2 |  30 | Urano
     4 |  30 | Mashita
     5 |  33 | Okajima
     8 |  28 | Takahashi

色々眺めた記事のうち一つ

Best use of Correlated subquery in sqlserver

RDBMS の view はいつ使われるか

StackOverflow より引用及び翻訳(意訳 加筆)

より正確な内容は本記事下段のリンク先参照

f:id:mat5ukawa:20160302230736j:plain

view はいくつか良いものを提供してくれる

1. View は複雑さを隠すことができる

何個かテーブルを結合したり、複雑な計算したり、
複雑ロジックを組んだクエリが必要な場合
こいつらは view に組込むことができる
また、view からはテーブルと同じように
select で属性を取得することができる

2. View はセキュリティメカニズムに使われうる

view は特定の行 必要ならば列を制約し 項目をテーブルから取得できる
その view にはパーミッションをつけることも可能だ
つまりこれは、ユーザーからの必要なデータだけを見える化した
「権限」を設けることだ

3. View はレガシーコードの簡潔化に使われうる

たくさんの「ぶっ飛んだテーブル」をリファクタしたい時
これらテーブルと同じ名を持った view へ置き換えが可能だ
view のスキーマは(ユーザーから見れば) 置き換わるテーブルスキーマと同じままで
リファクタされる実際のテーブルスキーマは変わることになる
( つまり「ユーザーから見れば何も変わってない」
 「リファクタする側からすれば色々手を加えられる」)
これで、「ぶっ飛んだテーブル」は自分の思いのままにリファクタができる

以上が view の有用な使い方の「数あるうち」だ
A view provides several benefits.

1. Views can hide complexity

If you have a query that requires joining several tables,
or has complex logic or calculations,
you can code all that logic into a view,
then select from the view just like you would a table.

2. Views can be used as a security mechanism

A view can select certain columns and/or rows from a table,
and permissions set on the view instead of the underlying tables.
This allows surfacing only the data that a user needs to see.

3. Views can simplify supporting legacy code

If you need to refactor a table that would break a lot of code,
you can replace the table with a view of the same name.
The view provides the exact same schema as the original table,
while the actual schema has changed.
This keeps the legacy code that references the table from breaking,
allowing you to change the legacy code at your leisure.

These are just some of the many examples of how views can be useful.

stackoverflow.com