Рельсы до Ханами | Кодементор

Пару месяцев назад я обнаружил небольшое сообщество разработчиков ruby, которые занимались созданием нового веб-фреймворка, Ханами. С тех пор я довольно внимательно слежу за ходом проекта. В последней версии Hanami интегрирована с ПЗУ в своем слое данных, поэтому я решил, что неплохо было бы немного поиграть с ним и создать простой REST API, чтобы посмотреть, как это работает.

Мы собираемся создать REST JSON API для классического приложения TODO.

Конечные точки

Списки

  • GET — получить список списков (/lists)
  • GET — получить список (/lists/:id)
  • POST — Создать список (/lists)

Задачи

  • GET — получение задач (/lists/:list_id/tasks)
  • GET — получение задачи (/lists/:list_id/tasks/:id)
  • POST — создать задачу (/lists/:list_id/tasks)

Сущности

Список

  • идентификатор (целое число)
  • имя (строка)

Задача

  • идентификатор (целое число)
  • описание (строка)
  • list_id (целое число)

Начнем с создания проекта. Hanami поставляется с командой инициализации, как и rails.

hanami new hanami_todo_app --database=postgres
cd hanami_todo_app
bundle install

После запуска новой команды Ханами должна была создать новый каталог с именем hanami_todo_app. Мы можем cd в каталог и взгляните на структуру. архитектура приложений Hanami немного отличается от rails. Приложение разделено на два основных каталога: lib и apps. В каталоге lib будет находиться большая часть вашей бизнес-логики. Это включает в себя ваши сущности (почти как модель Rails, но без API сохраняемости, подробнее об этом позже), сервисы, интеракторы или любой другой дизайн организации кода, который вы используете для своего приложения. В папке приложений у вас, скорее всего, будет только одна папка в простом приложении. У нас будет только одна папка с именем api то есть где будут сидеть все наши http действия. Если у вас есть большое приложение, у вас, вероятно, будет несколько папок в приложениях, например, admin (административный пользовательский интерфейс), web и т. д. В Rails есть механизмы для разделения большого приложения на разные модульные приложения меньшего размера.

По умолчанию Hanami создаст приложение по умолчанию с именем webмы удалим это приложение и создадим новое с именем api просто чтобы помочь моему обсессивно-компульсивному расстройству.

hanami destroy app web
hanami generate app api

После выполнения этой команды мы должны были удалить web папка под apps и новый api папка должна была быть создана.

Сущности и репозитории (модели)

Сейчас мы приступим к моделированию нашей системы. Двух простых объектов должно быть достаточно, List и Task.

➜ hanami_todo_app git:(master) ✗ hanami generate model list
      create lib/hanami_todo_app/entities/list.rb
      create lib/hanami_todo_app/repositories/list_repository.rb
      create db/migrations/20170531214217_create_lists.rb
      create spec/hanami_todo_app/entities/list_spec.rb
      create spec/hanami_todo_app/repositories/list_repository_spec.rb

➜ hanami_todo_app git:(master) ✗ hanami generate model task
      create lib/hanami_todo_app/entities/task.rb
      create lib/hanami_todo_app/repositories/task_repository.rb
      create db/migrations/20170531214247_create_tasks.rb
      create spec/hanami_todo_app/entities/task_spec.rb
      create spec/hanami_todo_app/repositories/task_repository_spec.rb

Как видно из вывода команды, Hanami создала сущность, но также создала и репозиторий. Мы обсудим их позже. Теперь мы можем перейти к сгенерированным файлам миграции и адаптировать их так, как нам нужно.

Hanami::Model.migration do
  change do
    create_table :lists do
      primary_key :id
      column :name, String, null: false

      column :created_at, DateTime, null: false
      column :updated_at, DateTime, null: false
    end
  end
end
Hanami::Model.migration do
  change do
    create_table :tasks do
      primary_key :id
      column :description, String, null: false
      foreign_key :list_id, :lists, on_delete: :cascade, null: false

      column :created_at, DateTime, null: false
      column :updated_at, DateTime, null: false
    end
  end
end

DSL миграции Hanami очень похож на тот, который предоставляет ActiveRecord. Нам нужно указать имя столбца, тип данных и любое ограничение, которое мы хотим для столбца. В этом случае мы добавили ограничение на допустимость значений NULL, чтобы обеспечить столбцы, отличные от NULL. Hanami автоматически генерирует средства доступа и установки для наших сущностей на основе схемы базы данных, как и рельсы.

Для запуска миграции мы можем использовать hanami db migrateтак как мы еще не создали базу данных, мы можем запустить hanami db prepare который будет создавать и запускать миграции для нас. Для большего количества задач, связанных с базой данных, вы можете проверить их с помощью hanami db.

Одно важное различие между Hanami и Rails в отношении моделирования данных заключается в том, что Hanami не предоставляет огромный API для классов сущностей. Сущности — это очень простые рубиновые классы, которые наследуются от Hanami::Entity. У них нет API для запроса, создания или обновления записей базы данных, ответственность за выполнение всех этих задач инкапсулирована в репозитории, связанном с сущностью (помните, мы упоминали о них?).

Это изменение сильно повлияет на дизайн вашей системы. Отделяя постоянство от модели, вы не связываете свою бизнес-логику с базовым хранилищем данных. Каждый репозиторий имеет собственное независимое хранилище данных, в нашем случае ListRepository и TaskRepository будут сохраняться в БД postgres.

Давайте представим, что наше приложение получает много трафика, и мы обнаружили, что наша база данных SQL является узким местом. Одним из возможных решений проблемы может быть перемещение часто используемых данных в более быстрое хранилище данных, например, Redis. Вместо рефакторинга всего нашего кода мы можем просто изменить наш репозиторий, чтобы начать использовать другое хранилище данных, и нам не нужно будет фактически касаться нашего класса сущности. Это очень простой и наивный пример того, как это может быть полезно. Надеюсь, вы поняли идею.

Мы собираемся играть с hanami console и создайте некоторые объекты, используя репозиторий.

# Let create a new repository instance
2.3.1 :011 > list_repository = ListRepository.new
 => #<ListRepository relations=[:lists]>

# Create and store a new list object
2.3.1 :012 > list_repository.create(name: 'Grocery')
[hanami_todo_app] [INFO] [2017-05-31 19:08:39 -0300] (0.000608s) SELECT "id", "name", "created_at", "updated_at" FROM "lists" LIMIT 1
[hanami_todo_app] [INFO] [2017-05-31 19:08:39 -0300] (0.003811s) INSERT INTO "lists" ("name", "created_at", "updated_at") VALUES ('Grocery', '2017-05-31 22:08:39.068319+0000', '2017-05-31 22:08:39.068319+0000') RETURNING *
 => #<List:0x007fa244403380 @attributes={:id=>1, :name=>"Grocery", :created_at=>2017-05-31 22:08:39 UTC, :updated_at=>2017-05-31 22:08:39 UTC}>

Репозитории предоставить API для взаимодействия с уровнем сохраняемости. Из документов Ханами:

Объект, являющийся посредником между сущностями и уровнем сохраняемости. Он предлагает стандартизированный API для запроса и выполнения команд в базе данных.

Методы, которые вы использовали для вызова в своих моделях Rails, теперь находятся в репозитории, поэтому вместо выполнения User.create({ ... }) вы будете делать UserRepository.new.create({ ... }). То же самое относится к find и обновить, я предлагаю посмотреть документы для лучшего понимания, так как API не совсем то же самое.

Репозитории также инкапсулируют ваши пользовательские запросы. В Rails вы, вероятно, создали некоторые области в модели, чтобы инкапсулировать логику вашего запроса. В Hanami вы пишете методы в своем репозитории. Одно большое различие между Hanami и Rails в этом случае заключается в том, что Hanami не предоставляет метод для выполнения пользовательских запросов вне класса Repository. Это действительно хорошая идея, потому что вам придется писать всю логику запросов в одном месте, а не распределять запросы по всей кодовой базе. 😃.

Вот так выглядит наш ListRepository.

class ListRepository < Hanami::Repository
  associations do
    has_many :tasks
  end

  def with_name(name)
    lists.where(name: name).as(List).to_a
  end

  def find_with_tasks(id)
    aggregate(:tasks).where(id: id).as(List).one
  end
end

Как видите, у нас есть with_name который скрывает логику запроса нашего хранилища данных для списка с заданным именем.

Другим важным элементом, который мы определяем в репозитории, являются ассоциации. Это то же самое понятие, которое мы имеем в ActiveRecord, он определяет, как наши различные сущности/модели соотносятся друг с другом. Эти определения ассоциации будут полезны, когда мы хотим запрашивать данные на основе нескольких таблиц. На данный момент Hanami поддерживает только has_many ассоциации, но вы можете использовать ветку разработки, чтобы получить belongs_to ассоциации тоже. Ассоциации очень сильны, они питаются от ROM. На данный момент эта функция является экспериментальной, но я думаю, она станет стабильной, как только все больше людей начнут использовать Hanami.

Действия (действия контроллера Rails)

Мы можем перейти к нашему последнему шагу, чтобы заставить наш API работать, действия (такие же, как методы контроллера в Rails).

Rails использует контроллеры для инкапсуляции обработчиков HTTP-запросов, которые есть в вашем приложении. Hanami решила использовать один класс для каждого действия вместо контроллера, который группирует действия данного ресурса.

Давайте используем генераторы для создания классов действий. Мы можем hanami generate action api 'lists#index' для создания действия индекса.

Генератор добавит правильные маршруты в ваш файл конфигурации/маршрутов, поэтому вам не нужно об этом беспокоиться.

module Api::Controllers::Lists
  class Index
    include Api::Action
    include JSONAPI::Hanami::Action

    def call(params)
      self.data = list_repository.all
      self.status = 200
    end

    def list_repository
      @list_repository ||= ListRepository.new
    end
  end
end

Важным методом здесь является call. Этот метод будет вызываться при запросе маршрута, связанного с этим действием. С помощью драгоценного камня gem 'jsonapi-hanami' (не забудьте добавить его в свой Gemfile), мы можем сериализовать наши списки в действительный ответ jsonapi. Для получения дополнительной информации об этом прекрасном драгоценном камне взгляните на здесь.

Давайте теперь рассмотрим другое действие, которое получает данные от клиента.

module Api::Controllers::Lists
  class Create
    include Api::Action
    include JSONAPI::Hanami::Action

    deserializable_resource :list

    params do
      required(:list).schema do
        required(:name)
      end
    end

    def call(params)
      self.data = list_repository.create(params[:list])
      self.status = 200
    end

    def list_repository
      @list_repository ||= ListRepository.new
    end
  end
end

В этих действиях у нас есть некоторые другие элементы для обсуждения. Первый, deserializable_resource говорит синтаксическому анализатору проанализировать входящую полезную нагрузку в поисках list элемент, это необходимо для анализа действительной полезной нагрузки jsonapi. Вторым важным аспектом является проверка, которую мы выполняем для параметров. Как видите, мы вызываем метод класса params, используя специальный DSL, чтобы добавить ограничения к нашим входным данным. Это питание от сухая проверка. Взгляните на документы для получения дополнительной информации о том, как проверять данные, это действительно мощный инструмент.

Вывод

Мы видели очень простой обзор различных строительных блоков, которые Hanami предлагает в посте. Есть МНОГО больше в Ханами, что я призываю всех исследовать, сообщество действительно потрясающее, и люди очень полезны, вы можете найти их на сетка.

Если вы хотите взглянуть на исходный код, вы можете найти его здесь bilby91/hanami-todo-приложение

Если у вас есть какие-либо вопросы или вы хотите изучить различные части Ханами, напишите комментарий. Я только начинаю работать с фреймворком, поэтому я хотел бы изучить различные компоненты проекта.

Наслаждайтесь 🎉

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *