Slide 1

Slide 1 text

Modular Architecture Газизов Альберт

Slide 2

Slide 2 text

Рельсы это здорово:)

Slide 3

Slide 3 text

Rails MVC

Slide 4

Slide 4 text

ActiveRecord class Contact < ActiveRecord::Base include Extensions::Sluggable MAX_IMAGE_SIZE = configatron.max_image_size SMALL_IMAGE_WIDTH = configatron.contact.small_image_width has_many :parts, dependent: :destroy has_many :emails has_one :contact_stat belongs_to :user has_one :company attr_accessible :first_name, :last_name, :position, :created_on, :small_image, :image, :additional_image, :image, :parts_attributes, :group_list :tag_list, :user_id, :slug, :old_slug accepts_nested_attributes_for :parts, allow_destroy: true mount_uploader :small_image, SmallImageUploader mount_uploader :image, ImageUploader acts_as_taggable_on :aliases, :tags slugize :name validates :created_on, :group_list, presence: true validates :fiirst_name, presence: true, uniqueness: true ...

Slide 5

Slide 5 text

Преимущества •  высокая скорость разработки на старте •  много готовых решений •  низкий порог вхождения

Slide 6

Slide 6 text

Недостатки •  сложность растет экспоненциально •  сложно поддерживать •  сложно добавлять новых разработчиков

Slide 7

Slide 7 text

Как бороться со сложность?

Slide 8

Slide 8 text

Modular Architecture

Slide 9

Slide 9 text

Преимущества •  Уменьшение сложности •  Легче поддерживать •  Легче вводить новых разработчиков •  Легче масштабировать

Slide 10

Slide 10 text

Недостатки •  Медленно на старте* •  Нужен опыт

Slide 11

Slide 11 text

Task Tracker

Slide 12

Slide 12 text

Агрегаты

Slide 13

Slide 13 text

Use Cases •  Регистрация в системе •  Создание проекта •  Создание таска •  Изменение статуса таска •  Комментирование таска •  ...

Slide 14

Slide 14 text

Создание таска class TaskTracker::Service::CreateTaskService attr_reader :accessor, :tasks_repository, :task_builder def initialize(accessor, tasks_repository, projects_repository, project_members_repository) @tasks_repository = tasks_repository @task_builder = TaskTracker::Builders::TaskBuilder.new( accessor, projects_service, project_members_repository ) end def create(attributes) task = task_builder.build(attributes) tasks_repository.put(task) endend

Slide 15

Slide 15 text

class Entities::Task attr_accessor :id, :title, :description attr_reader :status, :author_id, :project_id, :subtasks def initialize(title:, description: nil, project_id:, author_id:) Utils::ArgsValidator.not_blank!(title, :title) Utils::ArgsValidator.not_nil!(project_id, :project_id) Utils::ArgsValidator.not_nil!(author_id, :author_id) @title, @description, @project_id, @author_id, @subtasks = title, description, project_id, author_id, [] end def change_status(new_status) raise Common::Errors::InvalidParameter, "status can't be set to '#{new_status}'" unless @status.allowed_next_status?(new_status) @status = new_status end def title=(title) Utils::ArgsValidator.not_blank!(title, :title) @title = title end def id=(id) Utils::ArgsValidator.immutable!(@id, id, :id) @id = id end def build_subtask(title:) Entities::Subtask.new(title: title).tap do |subtask| subtasks << subtask end endend

Slide 16

Slide 16 text

class TaskTracker::Builders::TaskBuilder attr_reader :accessor, :projects_repository, :project_members_repository def initialize(accessor, projects_repository, project_members_repository) @accessor, @projects_repository, @project_members_repository = accessor, projects_repository, project_members_repository end def build(title:, description: nil, project_id:, subtasks: []) task = Entities::Task.new(title: title, description: description, project_id: project_id, author_id: accessor.id) subtasks.each do |title:| task.build_subtask(title: title) end ensure_project_exists!(project_id) ensure_accessor_is_project_member!(project_id) task end private def ensure_project_exists!(project_id) unless projects_repository.exists?(id: project_id) raise Common::Errors::InvalidParameter, "project with id '#{project_id}' doesn't exists" end end def ensure_accessor_is_project_member!(project_id) unless project_members_repository.exists?(project_id: project_id, member_id: accessor._id) raise Common::Errors::AuthorizationError, "you don't have access to the project '#{project_id}'" end endend

Slide 17

Slide 17 text

class Repository::TasksRepository attr_reader :tasks def initialize @tasks = {} end def put(task) task.id = tasks.size tasks[task.id] = task task end def find(id) tasks[id] end def delete(task) tasks.delete(task.id) endend

Slide 18

Slide 18 text

Валидации 1. внутри сущности + в билдере 2. в валидаторе 3. в слое выше + 1 вариант

Slide 19

Slide 19 text

Что получилось?

Slide 20

Slide 20 text

Пограничный слой

Slide 21

Slide 21 text

Обязанности пограничного слоя •  Кастинг params •  Синтаксический анализ params •  Валидация* •  Вызов сервиса БЛ •  Сериализация ответа сервиса в DTO(хеш)

Slide 22

Slide 22 text

TasksBoundary class Boundary::TasksBoundary attr_reader :tasks_repository, :projects_repository, :project_members_repository def initialize(accessor) tasks_repository = Repository::TasksRepository.new projects_repository = Repository::ProjectsRepository.new project_members_repository = Repository::ProjectMembersRepository.new end def create(params) params = Boundary::Casters::Tasks::CreateFormCaster.cast!(params) create_task_service = TaskTracker::Service::CreateTaskService.new(accessor, projects_repository, project_members_repository) task = create_task_service.create(params) Boundary::Serializers::TaskSerializer.serialize(task) endend

Slide 23

Slide 23 text

Caster & Serializer class Boundary::Casters::Tasks::CreateFormCaster include Hcask::Caster attributes do string :title string :description, optional: true array :subtask, each: :hash, optional: true do string :title end endend class Boundary::Serializers::TaskSerializer include ActiveSerializer::ObjectSerializer serialization_rules do attributes :id, :title, :description, :status resources :subtasks do attributes :title end endend

Slide 24

Slide 24 text

Результат

Slide 25

Slide 25 text

А дальше...

Slide 26

Slide 26 text

Заключение •  Легче поддерживать код •  Легче добавлять новых разработчиков •  RubyOnRails и БД - деталь