domain modelの延長討論


詳細
domain modelは、レルムモデルとも呼ばれ、Javaエンタープライズアプリケーションの議論の話題であり、JavaEyeもこの話題を何度も議論しています.簡単な例を見てみましょう.
参照
1つの簡単な会社の労働時間管理システム、従業員の個人情報を記録して、各従業員の仕事の任務の分配、および仕事の所属するカテゴリ(例えば開発して、まだテストして、それとも育成訓練など)、その中の各従業員はn個の任務があって、従業員と任務は1対の多関系で、各従業員もそれぞれ複数の異なる仕事のカテゴリに属して、従業員とタイプは多対多の関連関係であり、各タスクもそれぞれ一意の作業カテゴリに属し、タスクとカテゴリは多対一の関係である.また、システムは部門情報のメンテナンスを要求せず、departmentテーブルは必要ありません.したがって、このシステムでは4つのデータベース・テーブルが使用されます.
usersテーブルは従業員情報を保存し、name、password、gender、department、salaryがあります.
tasksテーブルは作業タスク情報を保存し、name、start_があります.time, end_time
kindsテーブル保存作業所属カテゴリ、name
kinds_usersテーブルは、usersテーブルとkindsテーブルの複数対の複数の関連外部キーを保存する関連テーブルです.
システムの機能要件は次のとおりです.
1、ある部門で新入社員を採用する
2、ある部門の従業員の総給料の合計
3、ある従業員はすでに始まっているがまだ終わっていない任務
4、ある従業員に任務を割り当てる
5.すべてのユーザーが現在開始しているがまだ終了していないタスク
6、あるカテゴリに対して、このカテゴリに関連するすべての従業員に、一括でタスクを追加する
7、任務の統計機能に対して、ある種類を与えて、当月の総任務数を統計して、すでに任務数を完成して、任務数を完成していない
まずrubyを用いてシステムの分野モデルをどのように実現するかを見てみましょう.

class User < ActiveRecord::Base
  has_and_belongs_to_many :kinds
  
  has_many :tasks, :dependent => :destroy do
    def processing_tasks
      find :all, :conditions => ["start_time <= ? AND end_time is null", Time.now]
    end
  end
  
  def apply_task(task_name)
    self.tasks << Task.new(:name => task_name, :start_time => Date.today)   
  end   
    
  def self.all_processing_tasks
    Task.find :all, :conditions => ["start_time <= ? AND end_time is null AND user_id is not null",Time.now]
  end
end

class Task < ActiveRecord::Base
  belongs_to : owner, :class_name => 'User', :foreign_key => 'user_id'
  belongs_to :kind
  
  def self.current_month_tasks(kind)
    kind.tasks.current_month_tasks 
  end
end

class Kind < ActiveRecord::Base
  has_and_belongs_to_many :users
  
  has_many :tasks do
    def current_month_tasks
      month_begin = Date.today - Date.today.mday + 1
      month_end = Date.today - Date.today.mday + 30
      processing_tasks = find :all, :conditions => ["start_time <= ? AND end_time is null ", month_begin]
      processed_tasks = find :all, :conditions => ["end_time >= ? AND end_time <= ? ", month_begin, month_end]
      all_tasks = processing_tasks.clone
      all_tasks << processed_tasks unless processed_tasks.size == 0
      return all_tasks, processed_tasks, processing_tasks
    end
  end
  
  def add_batch_task_to_users(task_name)
    self.users.each do |user|
      task = Task.new(:name => task_name, :start_time => Date.today) 
      user.tasks << task
      self.tasks << task
    end  
  end
end

class Department
  def self.employee(username, department)   
    User.create(:name => username, :department => department)   
  end  
  
  def self.total_salary(department)
    User.sum :salary, :conditions => ["department = ?", department]
  end
end

1、ある部門で新入社員を採用する
Department.employee("robbin","   ")

2、ある部門の従業員の総給料の合計
Department.total_salary("   ")

3、ある従業員はすでに始まっているがまだ終わっていない任務
user.tasks.processing_tasks

4、ある従業員に任務を割り当てる
user.apply_task("  Java")

5.すべてのユーザーが現在開始しているがまだ終了していないタスク
User.all_processing_tasks

6、あるカテゴリに対して、このカテゴリに関連するすべての従業員に、一括でタスクを追加する
kind.add_batch_task_to_users("      ")

7、任務の統計機能に対して、ある種類を与えて、当月の総任務数を統計して、すでに任務数を完成して、任務数を完成していない
Task.current_month_tasks(kind)

ここで注目すべきは,RoRは充血した領域モデルを容易に採用することができ,すべてのビジネスロジックを関連するdomain modelに入れることができることである.ここでuser,task,kindはいずれもデータベーステーブルに対応するレルムモデルであり,departmentはデータベースに対応しない純粋なビジネスロジックのdomain modelである.全部で4つのrubyファイル、4つのdomain model、55行のコード、すべての書くコードはここにあります.コード量は確かに非常に少なく、各domain modelの粒度は比較的大きいです.
Javaの使い方を見てみましょう

public class User {
    private Long id;
    private String name;
    private String password;
    private String gender;
    private String department;
    private int salary = 0;
    private List tasks = new ArrayList();
    # omit getter/setter methods ......
}

# omit User's ORM Mapping file

public class Task {
    private Long id;
    private String name;
    private int duration = 0;
    private User owner;
    # omit getter/setter methods ......
}

# omit Task's ORM Mapping file

public class Kind { 
    ......
}

# omit Kind's ORM Mapping file

public interface UserDao {
    public void addUser(User user);
    public loadUserById(Long id);
    # omit CRUD and other persistent methods ......
    public List findByDeparment(String department);
}

public interface TaskDao {
    # omit CRUD and other persistent methods ......
}

public class UserDaoImpl {
    # omit implementations ......
}

public class TaskDaoImpl {
    # omit implementations ......
}


public class UserService {
    private UserDao userDao;
    public setUserDao(UserDao userDao) { this.userDao = userDao; }
    public int workload(User user) {
        int totalDuration = 0;
        for (Task task : user.getTasks()) {
            totalDuration += task.duration;
        }
        return totalDuration;
    }
    public employee(String username, String department) {
        User user = new User();
        user.setName(username);
        user.setDepartment(department);
        userDao.addUser(user);
    }
}

public class TaskService {
    private TaskDao taskDao;
    public void setTaskDao(TaskDao taskDao) { this.taskDao = taskDao }
    public applyTask(String taskName, User user) {
        Task task = new Task();
        task.setName(taskName);
        task.setUser(user);
        taskDao.addTask(task);
    }
}

public class DepartmentService {
    private UserDao userDao;
    public void setUserDao(UserDao userDao) { this.userDao = userDao; }
    private UserService userService;
    public void setUserService(UserService userService) { this.userService = userService; }
    public int totalSalary(String department) {
        ......
    }
    ......  
} 

# omit IoC Container weaving configuration's file


Javaバージョンの実装コードはよく知られているので、ほとんどのコードは省略されています.Javaバージョンでは、3つの永続オブジェクト、3つのマッピングXMLファイル、3つのDAOインタフェースと実装クラス、4つのServiceと実装クラス、1つのIoCのbean組立ファイル、合計21のファイルが必要です.すべての論理書き込みが完全で、コード行数は少なくとも千行です.
対照的に,Javaの比較的ポピュラーな実装は貧血のモデルであり,オブジェクト向けの基本原則に従ってオブジェクトの状態はその挙動とカプセル化されるべきであるため,Javaが多く出てきたこれらのXXXXServiceは純粋な理論的観点から対応する持続的なオブジェクトに入れるべきであることがわかる.しかしJavaが充血モデルを実現するのは技術的に一定の難易度があり、どのようにサービス方法を永続的な対象に移すのだろうか.Daoの注入問題をどのように解決しますか?domain logicメソッドのトランザクションパッケージの問題をどのように解決しますか?前者はAspectJの静的織り込みによって解決することができ,後者は織り込みまたはannotation宣言によって解決することができるかもしれない.しかし、いずれにしても、Javaは技術的に充血モデルを実現することが難しく、また充血モデルを実現しても、Javaクラスの数百行のコードの状況を招き、そのコードの読み取り性、モジュールのレンコンを解く能力が悪くなるため、Javaは充血モデルに適していないと考えられており、複雑なビジネスロジックを表現する能力では、Javaはrubyよりずっと劣っている.
結論:
Javaにとって、貧血のモデルを採用するのに適しており、Javaは複雑なビジネスロジックをn個の小さなオブジェクトに分離するのに適しており、各小さなオブジェクトは単一の職責を記述し、n個のオブジェクトは互いに協力して複雑なビジネスロジックを表現し、このn個のオブジェクト間の依存と協力は外部の容器、例えばIoCを通じて明示的に管理する必要がある.しかし、具体的な対象ごとに貧血であることは間違いない.
この貧血のモデルのメリットは:
1、貧血対象ごとに職責が単一であるため、モジュールのレンコンを解く程度が高く、誤った隔離に有利である.
2、このモデルはソフトウェアアウトソーシングと大規模なソフトウェアチームの協力に非常に適していることが重要です.各プログラミング個体は,単一の職責を担う小オブジェクトモジュールの作成のみを必要とし,互いに影響を及ぼさない.
貧血モデルのデメリットは、
1、対象の状態と行為が分離されているため、1つの完全なビジネスロジックの記述は1つのクラスで完成することができず、互いに協力するクラスのグループが共同で完成する.そのため多重化可能な粒度は比較的に小さく、コード量の膨張が激しく、最も重要なのはビジネスロジックの記述能力が比較的に悪く、少し複雑なビジネスロジックでは、多すぎるクラスと多すぎるコードで表現する必要がある(私たちが仮定したこの簡単な工数管理システムのビジネスロジックの実現に対して、rubyは50行のコードを使用しているが、Javaは少なくとも千行のコードを必要としている).
2、オブジェクト連携は外部コンテナの組み立てに依存するため、裸でコードを書くことは不可能であり、外部のIoCコンテナを借りなければならない.
ルビーにとっては、充血モデルがより適しています.ruby言語の表現能力が非常に強いため、現在rubyを企業応用としているDSLは人気のある分野であり、DSLはある業界のビジネスロジックを記述するための専用言語であることを明らかにした.
充血モデルのメリットは次のとおりです.
1、対象の自己無撞着度が高く、表現能力が強いため、複雑な企業業務ロジックの実現に非常に適しており、多重化可能度が高い.
2、外部容器の組み立てに依存する必要はないので、RoRにはIoCの概念はない.
充血モデルのデメリットは次のとおりです.
1、対象の高度な自己交渉の結果、大規模なチームの分業協力に不利である.1つのプログラミング個体は、少なくとも1つの完全なビジネスロジックの機能を完了しなければならない.単一の完全なビジネスロジックでは、これ以上細分化することはできません.
2、業務ロジックの変動に伴い、領域モデルが比較的頻繁に変動する可能性があり、領域モデルが不安定であることもweb層コードの頻繁な変動をもたらす.
添付ファイルは、完全なRoRバージョンのプロジェクトサンプルコードです.これを実行するには、MySQLデータベース(InnoDBテーブルタイプ)、Ruby、Ruby on rails環境をインストールする必要があります.MySQLデータベースでdemoデータベースとdemo_をそれぞれ作成testデータベース、democonfigdatabase.ymlのMySQLデータベース構成を変更し、データベースパスワードに変更します.次に、プロジェクトとディレクトリの下で実行します.
rake db:migrate
rake db:test:clone_structure
rake test
すなわち、開発環境データベースを作成し、テスト環境データベースを作成し、すべてのユニットテストを実行します.レルムモデルコードはdemoappmodelsディレクトリの下にあります.ユニットテストコードはdemotestunitsディレクトリの下にあります
  • demo.zip (80.6 KB)
  • 説明:完全なRoRバージョンのプロジェクトサンプルコード
  • ダウンロード回数:576