Создаем веб-сервисы на рельсах!
Ввиду ограниченности доступной документации по использованию набирающего популярность фреймворка Ruby-on-Rails, я решил опубликовать собственный опыт по использованию RoR для создания веб-сервисов.
В этой статье мы будем создавать сервис учета и выдачи телефонных номеров из имеющегося пула.
Для этого мы будем использовать пакет ActionWebService, входящий в состав рельс. По ходу реализации будет создан веб-сервисный интерфейс, создана база номеров и даже будет проведена проверка совместимости созданного веб-сервиса с внешней системой Sonic ESB, используемой для оркестровки различных сервисов.
Итак, приступим:
1. Обновляем версии пакетов
D:\InstantRails\rails_apps\aws>gem update actionwebservice --include-dependenciesUpdating installed gems...Attempting remote update of actionwebserviceSuccessfully installed actionwebservice-1.2.3Installing ri documentation for actionwebservice-1.2.3...Installing RDoc documentation for actionwebservice-1.2.3...Gems: [actionwebservice] updatedD:\InstantRails\rails_apps>gem update rails --include-dependenciesUpdating installed gems...Bulk updating Gem source index for: http://gems.rubyforge.orgAttempting remote update of railsSuccessfully installed rails-1.2.3Gems: [rails] updated
Если что-то будет глючить, можно посмотреть вот эту статью
2. Создаём новое приложение
D:\InstantRails\rails_apps>rails aws create create app/controllers create app/helpers create app/models create app/views/layouts create config/environments create components create db create doc create lib create lib/tasks create log create public/images create public/javascripts create public/stylesheets create script/performance create script/process create test/fixtures create test/functional create test/integration create test/mocks/development create test/mocks/test create test/unit create vendor create vendor/plugins create tmp/sessions create tmp/sockets create tmp/cache create tmp/pids create Rakefile create README create app/controllers/application.rb create app/helpers/application_helper.rb create test/test_helper.rb create config/database.yml create config/routes.rb create public/.htaccess create config/boot.rb create config/environment.rb create config/environments/production.rb create config/environments/development.rb create config/environments/test.rb create script/about create script/breakpointer create script/console create script/destroy create script/generate create script/performance/benchmarker create script/performance/profiler create script/process/reaper create script/process/spawner create script/process/inspector create script/runner create script/server create script/plugin create public/dispatch.rb create public/dispatch.cgi create public/dispatch.fcgi create public/404.html create public/500.html create public/index.html create public/favicon.ico create public/robots.txt create public/images/rails.png create public/javascripts/prototype.js create public/javascripts/effects.js create public/javascripts/dragdrop.js create public/javascripts/controls.js create public/javascripts/application.js create doc/README_FOR_APP create log/server.log create log/production.log create log/development.log create log/test.log
3. Разрабатываем дизайн сервиса
Сервис будет использоваться внешним ИТ-приложением через SOAP-интерфейс. А также сервис должен иметь административный интерфейс, через который можно было бы завести пул номеров, удалить или отредактировать их значения.
Операции, которые будут использоваться внешним приложением:
- запрос доступных номеров, сервис должен предложить доступный номер
- резервирование номера, что значит что данный номер принадлежит определенному абоненту
- снятие резервирование с номера
Для идентификации абонента используется текстовая строка, например, содержащая URI от OpenID абонента
4. Создаем описание сервисов
Создаем web_service с методами suggest, allocate и free:
D:\InstantRails\rails_apps\aws>ruby script/generate web_service Telnum suggest allocate free exists app/apis/ exists app/controllers/ exists test/functional/ create app/apis/telnum_api.rb create app/controllers/telnum_controller.rb create test/functional/telnum_api_test.rbФайл telnum_api.rb содержит описание интерфейса Telnum API
class TelnumApi < ActionWebService::API::Base api_method :suggest api_method :allocate api_method :freeend
5. Пишем реализацию сервисов
Сначала уточняем методы интерфейса, указывая типы параметров принимаемых на входе и выдаваемых на выходе.
Редактируем файл telnum_api.rbclass TelnumApi < ActionWebService::API::Base api_method :suggest, :expects => [:string], :returns => [:string] api_method :allocate, :expects => [:string, :string], :returns => [:int] api_method :free, :expects => [:string, :string], :returns => [:int]endДалее редактируем контроллер telnum_controller.rb, пока что реализуя функциональность в виде заглушек, а также указываем привязку к TelnumAPI.
class TelnumController < ApplicationController wsdl_service_name 'Telnum' web_service_api TelnumApi def suggest(openid) return "1230001" end def allocate(openid, phone) return 1 end def free(openid, phone) return 1 endendТаким образом, сейчас реализация нашего сервиса всегда выдает успешный ответ на запросы о резервировании и освобождении номера phone абонентом openid. А также на запрос доступных номеров всегда предлагает 1230001.
Для того чтобы проверить получившийся API, воспользуемся инструментом в составе пакета ActionWebService, который создает html-интерфейс для работы с веб-сервисом. Для этого в классе контроллера указываем web_service_scaffold:
class TelnumController < ApplicationController wsdl_service_name 'Telnum' web_service_api TelnumApi web_service_scaffold :invoke def suggest(openid) return "1230001" end…
6. Первый запуск сервиса
D:\InstantRails\rails_apps\aws>ruby script/server=> Booting Mongrel (use 'script/server webrick' to force WEBrick)=> Rails application starting on http://0.0.0.0:3000=> Call with -d to detach=> Ctrl-C to shutdown server** Starting Mongrel listening at 0.0.0.0:3000** Starting Rails with development environment...** Rails loaded.** Loading any Rails specific GemPlugins** Signals ready. INT => stop (no restart).** Mongrel available at 0.0.0.0:3000** Use CTRL-C to stop.
7. Проверка работоспособности
Открываем в браузере http://127.0.0.1:3000/Telnum/invoke
Вызываем метод suggest с текстовым параметром:
Получаем ответ и видим какой был послан запрос и какой получен ответ в SOAP:
Также можно посмотреть описание веб-сервиса в виде WSD по ссылке http://127.0.0.1:3000/Telnum/wsdl
8.Использование сервиса из внешней системы
Из-за большого количества скриншотов вынес в отдельную статью:
Подключение веб-сервиса в сервисной шине Sonic ESB
9. Разделяем контроллер и сервис
Итак, мы убедились в работоспособности сервиса с точки зрения поддержки протокола SOAP.
Теперь надо и функциональность реализовать.
Для начала рекомендую разделять функциональность сервиса и контроллер, который обуспечивает работу SOAP-методов. Для этого мы создадим класс TelnumService, а в контроллере будем использовать его инстанс.
Создаем файл /apps/apis/telnum_service.rb и помещаем туда наши функции из контроллера. class TelnumService < ActionWebService::Base web_service_api TelnumApi def suggest(openid) return "1230001" end def allocate(openid, phone) return 1 end def free(openid, phone) return 1 endend
Теперь, когда функциональность перенесена в класс TelnumService, создаем его инстанс в контроллере, а также указываем что режим работы с сервисом теперь у нас многоуровневый.
class TelnumController < ApplicationController web_service_dispatching_mode :layered wsdl_service_name 'Telnum' web_service_scaffold :invoke web_service :telnum, TelnumService.new end
10. Создаем модель для БД
Наконец-то сделаем шаг, который давно уже напрашивался — создадим таблицу для хранения телефонных номеров и их резервирований.
Будем использовать класс Telnum, который будет автоматически сохраняться в таблице telnums.
D:\InstantRails\rails_apps\aws>ruby script/generate model telnum exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/telnum.rb create test/unit/telnum_test.rb create test/fixtures/telnums.yml create db/migrate create db/migrate/001_create_telnums.rb
Кроме прочего, у нас создался файл app/models/telnum.rb с классом Telnum, а также db/migrate/001_create_telnums.rb, в котором мы можем прописать привязку к столбцам таблицы.
class CreateTelnums < ActiveRecord::Migration def self.up create_table :telnums do |t| t.column :phone, :string t.column :openid, :string end end def self.down drop_table :telnums endend
Для того, чтобы создать таблицу с указанными столбцами используем механизм миграций, заложенный в фрймворк Ruby-on-Rails:
D:\InstantRails\rails_apps\aws>rake db:migrate(in D:/InstantRails/rails_apps/telnum) == CreateTelnums: migrating ==================================================-- create_table(:telnums) -> 0.0310s== CreateTelnums: migrated (0.0310s) =========================================
Видим, что таблица успешно создана.
11. Создаем CRUD-методы для заполнения значений telnum
Теперь необходимо реализовать административный интерфейс, позволяющий создавать, читать, редактировать и удалять объекты Telnum.
Не будем сильно мудрствовать и создавать сложный интерфейс, а возмользуемся инструментом скаффолдинга, который для нашей модели (класса Telnum), создаст набор CRUD-методов (create, read, update, delete).
D:\InstantRails\rails_apps\aws>ruby script/generate scaffold telnum exists app/controllers/ exists app/helpers/ create app/views/accounts exists app/views/layouts/ exists test/functional/ dependency model exists app/models/ exists test/unit/ exists test/fixtures/ identical app/models/telnum.rb identical test/unit/telnum_test.rb identical test/fixtures/telnums.yml create app/views/telnums/_form.rhtml create app/views/telnums/list.rhtml create app/views/telnums/show.rhtml create app/views/telnums/new.rhtml create app/views/telnums/edit.rhtml create app/controllers/telnums_controller.rb create test/functional/telnums_controller_test.rb create app/helpers/telnums_helper.rb create app/views/layouts/telnums.rhtml create public/stylesheets/scaffold.css
Проверим, что получилось.
Для этого стартуем наш сервис
D:\InstantRails\rails_apps\aws>ruby script/server --port=3001Теперь через веб-интерфейс заводим несколько номеров, чтобы заполнить пул.
12. Создаем более читаемый wsdl для сервера
Сделаем небольшое улучшение WSDL, для большей читаемости.
Для этого в описание в файле telnum_api.rb внесем названия параметров
class TelnumApi < ActionWebService::API::Base api_method :suggest, :expects => [{:openid=>:string}], :returns => [{:phones=>:string}] api_method :allocate, :expects => [{:openid=>:string}, {:phone=>:string}], :returns => [{:resultcode=>:int}] api_method :free, :expects => [{:openid=>:string}, {:phone=>:string}], :returns => [{:resultcode=>:int}]end
13. Связываем сервис с моделью
Кажется мы забыли самое главное — реализовать функциональность, которую мы вынесли в TelnumService.
- Добавляем поиск свободных номеров по базе
- Делаем резервирование, путем внесения идентификатора абонента в поле openid для выбранного номера
- Выполняем освобождение зарезервированного номера
class TelnumService < ActionWebService::Base web_service_api TelnumApi def suggest(openid) t = Telnum.find_by_openid("") return t.phone end def allocate(openid, phone) t = Telnum.find_by_phone(phone) t.openid = openid t.save return 1 end def free(openid, phone) t = Telnum.find_by_phone(phone) t.openid = "" t.save return 1 endend
14. Добавляем проверки корректности данных
Заметим, что если объект не был найден в базе, то мы обратимся к nil, и наш сервис обязательно упадет.
Добавим проверки данных:
class TelnumService < ActionWebService::Base web_service_api TelnumApi def suggest(openid) t = Telnum.find_by_openid("") return t.phone end def allocate(openid, phone) t = Telnum.find_by_phone(phone) if t == nil return 0 else t.openid = openid t.save return 1 end end def free(openid, phone) t = Telnum.find_by_phone(phone) if t == nil return 0 else t.openid = "" t.save return 1 end endendВсё хорошо, только наш сервис не следит за тем
чтобы резервируемый номер был свободен или что освобождаемый номер
принадлежит тому абоненту, который решил от него отказаться.
class TelnumService < ActionWebService::Base web_service_api TelnumApi def suggest(openid) t = Telnum.find_by_openid("") return t.phone end def allocate(openid, phone) t = Telnum.find_by_phone(phone) if t == nil return 0 else if t.openid == "" t.openid = openid t.save return 1 else return 0 end end end def free(openid, phone) t = Telnum.find_by_phone(phone) if t == nil return 0 else if t.openid == openid t.openid = "" t.save return 1 else return 0 end end endend
Сервис готов!
- Войдите на сайт для отправки комментариев