简体   繁体   中英

Multi-tenant rails application with a domain/subdomain architecture like Shopify

I'm building a multi-tenant rails application, similar to Shopify. Whenever a customer registers an Account , they will have a subdomain created at customer.myapp.com . This subdomain returns a view with data related to their account (including a /admin area).

Now in some cases, customers would like to use their own custom domain, instead of the subdomain created for them. How do I need to adjust my rails routes and controllers to return a view with data related to the customers account, based on not just the subdomain but on either the custom domain OR the subdomain?

This is how I've set up my config/routes.rb for handling subdomains:

# config/routes.rb
Rails.application.routes.draw do
  ...
  constraints(SubdomainRequired) do
    scope module: :accounts do
      ...
    end
  end
end

With app/constraints/subdomain_required.rb looking like this:

# app/constraints/subdomain_required.rb
class SubdomainRequired
  def self.matches?(request)
    request.subdomain.present? && request.subdomain != "www"
  end
end

And finally my app/controllers/accounts/base_controller.rb setting the current account, based on the requests subdomain:

# app/controllers/accounts/base_controller.rb
module Accounts
  class BaseController < ApplicationController
    before_action :set_current_account
    ...
    def set_current_account
      if request.subdomain.present?
        @current_account = Account.find_by(subdomain: request.subdomain)
        render_404 if !@current_account
      end
    end

    def render_404
      render file: "#{Rails.root}/public/404.html", status: 404
    end
    ...
  end
end

For the DNS setup I followed the approach of companies like Shopify. It looks like they have their customers create an A record that points to the primary IP address of their app. And they have them create a CNAME for the "www" subdomain, which points to a subdomain ( sites.myapp.com ), set up as a CNAME for the apps subdomain (in my case managed by DigitalOcean's app platform).

IF the DNS Settings work as intended, I'm left wondering how I can point requests in the right direction when the customer lands on my app. So for example if traffic hits customer.com and it has an A record pointing to my servers primary IP, plus a CNAME for sites.myapp.com , how do I handle the request and redirect it to the correct subdomain whilst keeping the user on their custom domain?

These are the resources I've checked so far:

Thanks for your help!

Okay, so this is what I came up with in the meantime.

First of all I left my config/routes.rb as described above. Everything seemed to be working as intended there.

Then I adjusted app/constraints/subdomain_required.rb to not only check for the subdomain but also the domain part of the request:

# app/constraints/subdomain_required.rb
class SubdomainRequired
  def self.matches?(request)
    request.subdomain.present? && request.subdomain != "www" || 
      request.domain.present? && request.domain != ENV["APP_DOMAIN"]
  end
end

And last but not least I changed the set_current_account method in app/controllers/accounts/base_controller.rb to this monster of an if statement:

# app/controllers/accounts/base_controller.rb
module Accounts
  class BaseController < ApplicationController
    before_action :set_current_account
    ...
    def set_current_account
      @current_account = if request.domain.present? && request.domain != ENV["APP_DOMAIN"]
                           # Do custom domain stuff
                           if request.subdomain == "www" || !request.subdomain.present?
                             # Set current account by custom domain, e.g. custom.com
                             Account.find_by(domain: request.domain)
                           else
                             # Set current account by custom subdomain, e.g. site.custom.com
                             Account.find_by(domain: "#{request.subdomain + "." + request.domain}")
                           end
                         elsif request.subdomain.present? && request.subdomain != "www"
                           # Set current account by subdomain, e.g. site.myapp.com
                           Account.find_by(subdomain: request.subdomain)
                         end
      render_404 unless @current_account
    end
    ...
  end
end

As of now I'm still testing my solution. Might need some more tweaking and definitely a little bit of refactoring but it seems to be working.

Another (unexpected) problem that I had to solve was the whole DNS setup. Similar to heroku, DigitalOcean's app platform doesn't support static IP addresses and therefore a client wouldn't be able to just create an A record, pointing to my app's IP address, since it could change at any time.

For now I've set up a forwarding proxy (is this how you call it?) with NGINX, based on the following example for heroku: https://help.heroku.com/YTWRHLVH/how-do-i-make-my-nginx-proxy-connect-to-a-heroku-app-behind-heroku-ssl

Hope that helps anybody out there, looking for a similar solution.

Let me know if I need to elaborate.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM