Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Composition & Domain Model

Composition & Domain Model

My speech was about the creation of business entities in code through the composition approach. I explained why it is better than inheritance and why ActiveRecord as a pattern is a bad idea for projects with complicated business logic. We also discussed why some of the solutions called “Rails-Way” are anti-patterns.

Volodymyr Melnyk

May 30, 2015
Tweet

More Decks by Volodymyr Melnyk

Other Decks in Programming

Transcript

  1. Why do we use STI? • We need STI when

    there is a necessity to work with a collection of entities of all subtypes, what is a pretty uncommon case itself and can be solved easily without STI. • We use STI because we know about Barbara Liskov’s Substitution Principle from SOLID, but we do not know about Composition approach. • We use STI because we don’t want additional relations to be created.
  2. Problems resulted by STI • “type” is a system (metadata)

    column, but pretty often we use it on Domain Model Layer. • “type” is difficult to be changed. • We can’t add a constraint on “type”. • Probably, we will need to create other inherited classes. • Each subtype will need subtype-specific columns/attributes. So we will get all the problems bounded with NULL. • We can’t use subtype specific primary key and constraints on RDBMS layer. • Sometimes we can’t use a super type to get a collection of entities of all subtypes.
  3. Example #1 class Comment < ActiveRecord::Base end class SuperComment <

    Comment has_many :not_so_super_comments end class NotSoSuperComment < Comment end Comment.joins(:not_so_supper_comments) ActiveRecord::ConfigurationError: Association named 'not_so_super_comments' was not found on Comment; perhaps you misspelled it?
  4. Composition as a solution Use one “Base” relation and additional

    relations for specific attributes and use DAOs over “Base” and other relations to create single entity. Put business logic into this entity.
  5. Why do we use Polymorphic associations (PA)? • If an

    entity of some type can belong to many other entities of different types, but only to one of them at one time. • To avoid the creation of many very similar entities and relations.
  6. Example #2 class Comment < ActiveRecord::Base belongs_to: :commetable, polymorphic: true

    end class Article < ActiveRecord::Base has_many :comments, as: :commentable end class Image < ActiveRecord::Base has_many :comments, as: commentable end
  7. Problems resulted by PA • Commenting may require some additional

    logic which will be duplicated in many places (Article, Photo, etc.). Moving this logic into separate module isn’t very helpful. • We can’t use JOINs because we really have no commentable relation and we can’t row-by-row change relation which we join. • If we need some additional attributes/columns for commentable entities - we will have to add them to all commentable entities/relations. (counter cache is the simplest example). • You can’t define association in metadata and can’t add constraints on foreign keys. • You can’t use polymorphic associations in the case when, for example, Cargo has two addresses: cargos.destination_point, cargos.start_point.
  8. Composition as a solution • We can create separate Commentable

    entity and “commentables” relation. • We can put all commenting- related attributes/columns and logic into this entity/relation. • We can create many different *Commentable entities and “*commentables” relations for logic and attributes/columns which depend on entity that we want to be commentable.
  9. Composition/DDD approach • Follow the previous leads. • Use DDD’s

    Aggregate pattern to describe dependency between Comments and Commentable and also between Commentable and Article, Photo, etc.
  10. ActiveRecord as a golden hummer "I suppose it is tempting,

    if the only tool you have is a hammer, to treat everything as if it were a nail.” - Abraham Maslow
  11. Why do we use ActiveRecord • It’s good when we

    want to map a relation onto an entity. • It’s a pretty good variant of DAO for the simplest cases, like stupid simple CRUD operations. • ActiveRecord comes out of the box with Rails.
  12. Problems resulted by ActiveRecord • Abstraction leaks on more complicated

    queries. Actually, it does even on not so complicated ones. • We can’t abstract from data storage with ActiveRecord. • It doesn’t allow to use many relations under the hood of entity. • AR’s models have too much responsibility.
  13. Composition/DDD approach • We can use DataMapper pattern. • We

    can use the most primitive DataMapper implementation which uses predefined SQL queries.
  14. Why do we use pseudo keys • because it’s hard

    sometimes to find good natural key. • as composite primary keys require composite foreign keys and we think that they are difficult in usage. • because they simplify using of CoC principle.
  15. Problems of pseudo-keys • In most cases we have no

    “id” in a domain model. • They doesn’t guarantee uniqueness of entity and we get doubles. • We need to make more JOINs. • Because of doubles it’s hard to make analytics. • It forces us to use DISTINCT. • We can’t use USING. • Pseudo-keys are platform dependent. It may be hard to move your DB from one RDBMS to another.
  16. Example #3 # We have no Category with id ==

    5 a = Article.create title: "New Article", category_id: 5
  17. Solution • Use natural keys if it’s possible. • Use

    composite foreign keys for composite primary keys. • Define associations with special expressions which automatically add constraints.
  18. Example #4 class AddKeys < ActiveRecord::Migration def change execute <<-SQL

    ALTER TABLE categories DROP COLUMN id; ALTER TABLE categories ADD COLUMN category_name varchar(36) primary key; ALTER TABLE articles ADD COLUMN category_name varchar(36) references categories(category_name); SQL end end
  19. Example #4 a = Article.create({ title: "My Article”, category_name: "unexistent

    category”}) PG::ForeignKeyViolation: ERROR: insert or update on table "articles" violates foreign key constraint “articles_category_name_fkey" DETAIL: Key (category_name)=(unexistent category) is not present in table "categories". c = Category.create category_name: "Programming" a = Article.create category_name: “Programming" SELECT * FROM articles JOIN categories USING(category_name);
  20. We use validation: • on forms on front-end. • inside

    controllers (strong params, for example). • inside models.
  21. Problems with Rails validation • Validation logic spreaded all around

    an app. • It’s hard to test because sometimes we need to create some associated records. • It’s not OO-way. It’s not compatible with the Single Responsibility Principle. • Sometimes we need additional logic to skip validation.
  22. Composition/DDD approach • We can put all validation of user

    input into different Form- objects. • We can put code, which responds for consistency into Entity or Aggregate.
  23. Problems with custom Types/Domains • Rails’s implementation of ActiveRecord pattern

    has no wrappers over original data to provide custom, more meaningful objects - ValueObjects, so we add a lot of logic into model. • It’s not the OO-way. It’s not compatible with the Single Responsibility Principle. • We have a lot duplications of code. • Our code is hard to test.
  24. Composition/DDD approach • We can wrap values into ValueObjects to

    provide additional logic and limitations on them. • After that we can use ValueObjects as attributes of Entity.
  25. Conclusion • Use Composition approach. • Use DDD. Develop your

    app on the top of Domain Model. • Use all features of SQL. • Learn relational theory. • Be careful about data consistency. • Don’t worry, be happy!