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

Delivering backend - Case study

Delivering backend - Case study

It's a short description of the project I've done recently. Focused on presenting stack, best practices, and patterns used. This our typical approach to Ruby backend at Netguru.

Michał Poczwardowski

March 21, 2018
Tweet

More Decks by Michał Poczwardowski

Other Decks in Programming

Transcript

  1. Questions • Client? Huge Business (from scratch - after PDS)

    / corpo • Problem Domain? Recruitment / Employer Branding • Team? Netguru + outside agency (responsible for UI and visuals) • Stack? Grape API + ActiveAdmin for Backend / Frontend@Ember
  2. README.md - minimum! • Project stack • Project setup •

    Detailed info about used patterns and solutions • Known Issues • How to get into staging? • How to get into production? • Deployments explained • Default seeds
  3. Stack / Gemfile (tip: keep them organized) #1 # rails

    gem "rails", "~> 5.1.3" # general gems gem "aasm" gem "activeadmin" gem "acts-as-taggable-on" gem "acts_as_list" gem "dry-monads" gem "jbuilder", "~> 2.5" gem "pg", "~> 0.18" gem "puma", "~> 3.7" gem "rails-i18n", "~> 5.0.0" gem "redis-namespace" gem "rollbar" gem "sidekiq"
  4. Stack / Gemfile #2 # authentication & authorization gem "devise"

    gem "devise_security_extension", git: "https://github.com/phatworx/devise_security_extension.git" # => rubygems.org devise_security_extension 0.10.0 version does not support rails 5.1 # => raising undefined method "before_filter" error gem "doorkeeper" gem "pundit" gem "rack-attack" gem "rack-cors"
  5. Stack / Gemfile #3 # grape + jsonapi gem "grape"

    gem "grape-jsonapi-resources" gem "grape-middleware-logger" gem "grape-swagger" gem "grape-swagger-rails" gem "hashie-forbidden_attributes" # to make grape params validation work
  6. Stack / Gemfile #4 # 3rd parties - APIs and

    processing gem "cloudinary" gem "griddler" gem "griddler-sendgrid" gem "httpi" # picked as a connection wizard because already needed by savon gem "nokogiri" gem "recaptcha", require: "recaptcha/rails" gem "savon", "~> 2"
  7. Endpoints Base class API::V1::Users::Base < Core namespace :users do mount

    ChangePassword mount Me mount Register route_param :id, type: Integer do before { @user = User.find(params[:id]) } mount Delete mount Show mount Update end end # app/controllers/api/v1/users/base.rb
  8. Example Endpoint - just authorize and service call class API::V1::Users::ChangePassword

    < Base desc "Change user password using current one" helpers Params helpers API::V1::Helpers::JSONAPIParams before { doorkeeper_authorize! } params do use :jsonapi_data_attributes, required: [{ name: "current-password", type: String, desc: "Current user password" }], use: [{ predefined: :_password_confirmable }] end patch "change-password" do authorize current_user, :change_password? assure_rightness ::UserServices::ChangePasswordUsingCurrent.call(current_user, jsonapi_attributes(params)) end end # app/controllers/api/v1/users/change_password.rb
  9. ApplicationService class UserServices::Register < ApplicationServiceInTransaction def call(attributes) Right(attributes) .bind(method(:ensure_agreements)) .bind

    { |attrs| ::GeneralServices::InjectModelIdsFromListInAttributes.call(attrs, :city) } .bind(method(:create_user)) end private def ensure_agreements(attributes) return Right(attributes) if required_agreements?(attributes) Left(I18n.t("user.registration.no_required_agreements")) end (...) end end # app/services/user_services/register.rb
  10. Models without logic and before/after actions class Message < ApplicationRecord

    belongs_to :author, class_name: "User", optional: true belongs_to :conversation has_many :activity_records, as: :trackable validates :content, presence: true end
  11. dry-monads http://dry-rb.org/gems/dry-monads/ Result The Result monad is useful to express

    a series of computations that might return an error object with additional information. The Result mixin has two type constructors: Success and Failure. The Success can be thought of as “everything went success” and the Failure is used when “something has gone wrong”. // http://dry-rb.org/gems/dry-monads/result/
  12. Why handle services using Either/Success monad • .succcess? and .failure?

    work the same for every service, • chainability, • universal error handling - the same interface for all services • ApplicationServiceInTransaction, • extract logic into shared pieces
  13. Integrations aka 3rd parties • KENEXA - WSDL/SOAP requests -

    recruitment cloud software, poorly documented, a lot of pain • Sendgrid Inbound Parse - handling incoming emails • Cloudinary - assets images/videos
  14. Secure headers https://github.com/twitter/secureheaders SecureHeaders::Configuration.default do |config| # CSP stands for

    Content Security Policy config.csp = { base_uri: %w('self'), block_all_mixed_content: true, child_src: %w('self'), connect_src: [], default_src: %w('self'), font_src: %w('self' data:), form_action: %w('self'), frame_ancestors: [], img_src: %w('self'), manifest_src: %w('self'), media_src: [], object_src: %w('self'), plugin_types: [], report_uri: [], sandbox: false, script_src: %w('self'), style_src: %w('self' 'unsafe-inline'), upgrade_insecure_requests: false, worker_src: %w('self'), } config.referrer_policy = %w(no-referrer-when-downgrade) end # config/initializers/secure_headers.rb