Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register Lotus applications #30

Closed
jodosha opened this issue Jun 30, 2014 · 13 comments · Fixed by #105
Closed

Register Lotus applications #30

jodosha opened this issue Jun 30, 2014 · 13 comments · Fixed by #105

Comments

@jodosha
Copy link
Member

jodosha commented Jun 30, 2014

The microservices architecture uses an anonymous Lotus::Router instance. Mounted applications aren't aware of the prefix that we assign in config.ru, this leads to wrong URL generation.

Example:

# config.ru
run Lotus::Router.new {
  mount Backend::Application, at: '/backend'
}

# code
Backend::Routes.path(:home) # => "/dashboard", but it should return "/backend/dashboard".

This double routing dispatch is inefficient: a request goes thru two instances of Router, one from config.ru, one from the application.

We can do introduce a new object like this:

# config.ru
run Lotus::Builder.new {
  mount Backend::Application, at: '/backend'
  mount Frontend::Application, at: '/'
}

It should:

  1. Pass to the single applications the :at prefix, so they are aware of the prefix configuration.
  2. Read all the routes from all the applications and try to compose a one-tier routing system (if possible).

Imagine the following scenario, where we have two applications with the following routes and controllers:

# apps/backend/application.rb
module Backend
  class Application < Lotus::Application
    configure do
      get '/dashboard', to: 'dashboard#index', as: :home # routes to Backend::Controllers::Dashboard::Index
    end
  end
end

# apps/frontend/application.rb
module Frontend
  class Application < Lotus::Application
    configure do
      get '/dashboard', to: 'dashboard#index', as: :home # routes to Frontend::Controllers::Dashboard::Index
    end
  end
end

With anoymous Lotus::Router:

                                                              /dashboard       +-------------------------------------------+
                                                                               |                                           |
                                                         +-------------------> |  Backend::Controllers::Dashboard::Index   |
                                    +-------------------++                     |                                           |
                                    |                   ||                     +-------------------------------------------+
                      /backend      |  Backend routes   ++                                                                  
+----------------+                  |                   |                                                                   
|                +----------------> +-------------------+                                                                   
|  Anonymous     |                                                                                                          
|  Router        |                                                                                                          
|                +----------------> +-------------------+                                                                   
+----------------+                  |                   |                                                                   
                      /             |  Frontend routes  ++                                                                  
                                    |                   ||                     +-------------------------------------------+
                                    +-------------------++                     |                                           |
                                                         +-------------------> |  Frontend::Controllers::Dashboard::Index  |
                                                                               |                                           |
                                                              /dashboard       +-------------------------------------------+

With registered applications (flatten routes)

                                            +-------------------------------------------+
                      /backend/dashboard    |                                           |
+----------------+                          |  Backend::Controllers::Dashboard::Index   |
|                +------------------------> |                                           |
|  Registered    |                          +-------------------------------------------+
|  Applications  |                                                                       
|                +------------------------> +-------------------------------------------+
+----------------+                          |                                           |
                      /dashboard            |  Frontend::Controllers::Dashboard::Index  |
                                            |                                           |
                                            +-------------------------------------------+

@lengarvey
Copy link
Contributor

@jodosha this seems like it should be an issue on https://github.com/lotus/router instead?

@jodosha
Copy link
Member Author

jodosha commented Jul 5, 2014

@lengarvey Lotus::Router is agnostic about Lotus applications and I'd like to keep it as it is. Probably it would require some new mechanism in Lotus::Router as base for this feature.

@lucasas
Copy link

lucasas commented Aug 8, 2014

I'm giving attention for this @jodosha. Maybe on Monday I send some code draft.

@jodosha
Copy link
Member Author

jodosha commented Aug 8, 2014

@lucasas 👍

@lucasas
Copy link

lucasas commented Aug 8, 2014

@jodosha where the application is creating this anonymous router? And where double routing dispatch is being made? Thanks dude

@jodosha
Copy link
Member Author

jodosha commented Aug 8, 2014

@lucasas The "anonymous router" is something like this:

# config.ru

# this router instance is anonymous
run Lotus::Router.new {
  mount Backend::Application, at: '/backend'
  mount Frontend::Application, at: '/'
}

It's the only way that we have now for dispatch HTTP requests for multiple applications.
The full explanation is in the README.

@lucasas
Copy link

lucasas commented Aug 12, 2014

Hey @jodosha, I've implemented a draft to accomplish flatten routes, registering a lot of applications.

Some changes were made on lotus-router to contemplate this. Now, Lotus::Router can receive an option named mount_at, which will be used to define a namespace for the defined routes. More details can be found here: lucasas/router@45ebf37#diff-7874fa6a25a9c35022a569e25b04bfa4

But basically, now you can do the follow:

Lotus::Routes.new(mount_at: 'backend') do
    get '/flowers',     to: 'flowers#index'
end

This is completelly equal with the code below:

Lotus::Routes.new do
    namespace 'backend' do
        get '/flowers',     to: 'flowers#index'
    end
end

Another change on Lotus::Router is that it now has an each method. This is useful to list (and copy) all routes defined into a Lotus::Router instance. So that you can iterate over these routes:

@router.each do |route|
   puts route # => it will print #to_str from Lotus::Routing::Route
end

Lotus itself, suffered some changes too.

Lotus::Configuration allows the user specify a mount point to the given Lotus::Application, this information is passed to Lotus::Router instance. More details here: lucasas@2b18990

module Backend
  class Application < Lotus::Application
    configure do
      root File.dirname(__FILE__)
      load_paths << [
        'controllers',
        'views'
      ]

      layout :backend

      # defining mount point of this application
      mount_at 'backend'

      routes do
        get '/login', to: 'sessions#new', as: :login
      end
    end
  end
end

The most important change ocurred here: lucasas@2b18990

The new class called Lotus::Routing::Builder is where the magic happens. There, Lotus copies all routes defined into each application storing it in a single Lotus::Router instance, the call method is redefined, and now is responsible to deal with all requests coming. It can be used like in the code below:

run Lotus::Routing::Builder.new {
  mount Backend::Application
  mount Frontend::Application
}

As each application defines its own mount_at point, it's not needed to pass at option when calling the method mount. But I'm not sure about this interface. Maybe, instead of call mount_at method inside configure, we can pass at option in the code above.

Next steps:

  • raise an error when two applications have the same mount point (aka mount_at)
  • improve documentation of all classes
  • create some integration tests to validate all those changes

I'm really want to know your opinion, realize if we are in the same page.

Thanks.

@lucasas
Copy link

lucasas commented Aug 19, 2014

Hey @jodosha. Thoughts here?

@lucasas
Copy link

lucasas commented Aug 22, 2014

There is a little bug here to be resolved (thanks @arthurgeek)

As my Lotus::Routing::Builder#call method is not calling @rendering_policy.render(response), Lotus::View is not working with my solution.

I've copied all routes from all applications to a single router, this router just call the recognized destination without pass through Lotus::Application#call which calls @rendering_policy.render(response).

I'm visualizing all possibilities before go ahead. If you guys, specially @jodosha, have some idea to help me, would be great.

One of them is: I could instantiate a new Lotus::RenderingPolicy, use it inside Lotus::Routing::Builder, but it depends on Lotus::Configuration which is unique for each application, so I think this is not a reasonable idea, since each application has its own configurations.

@jodosha
Copy link
Member Author

jodosha commented Aug 22, 2014

@lucasas Yes, I know, what you mean. Sorry for not making clear that this was the challenge of this feature, and instead you had to figure out it by yourself. I opened this ticket to brainstorm with you guys.

As we haven't already defined a stable architecture, my proposal is to hold on this, as it feels like premature optimization. We don't want to bend the framework too early.

@lucasas
Copy link

lucasas commented Aug 22, 2014

Yes, this is really a challenge. I spent today 6 hours trying to figure out how to solve this, and I was intending to work on it in the next days.

As you said, I might give up to do it for a while. But I think that step 1 (Pass to the single applications the :at prefix, so they are aware of the prefix configuration) should be done right now.

What is your opinion?

@lucasas
Copy link

lucasas commented Aug 22, 2014

As I talked with @jodosha, for while, I'll keep focus on the first feature (Pass to the single applications the :at prefix, so they are aware of the prefix configuration).

Actually, this is done, I'm gonna extract it, create some documentation and tests, before open a PR.

@lucasas
Copy link

lucasas commented Aug 23, 2014

Hey @jodosha, in my solution, the first step is coupled to second one, in a way that one doesn't work without the other.

I want to explain what I've done to resolve just the first step.

I've changed Lotus::Loader to respect a given router_namespace defined into Lotus::Application:

module Backend
  class Application < Lotus::Application
    configure do
      router_namespace 'backend'

      routes do
        get '/login', to: 'sessions#new', as: :login
      end
    end
  end
end

module Frontend
  class Application < Lotus::Application
    configure do
      router_namespace 'frontend'

      routes do
        get '/login', to: 'sessions#new', as: :login
      end
    end
  end
end

Into Lotus::Loader#load_rack! I've done:

      application.routes = Lotus::Router.new(
        resolver:    resolver,
        default_app: default_app,
        scheme:      configuration.scheme,
        host:        configuration.host,
        port:        configuration.port
      )
      application.routes.namespace(configuration.router_namespace, &configuration.routes)

It's simple, all routes are defined respecting a router_namespace. Doing that, when Backend::Router.path(:login) is called, /backend/login is returned, step one accomplished.

In my config.ru file, I'm using an instance of Lotus::Routing::Builder:

module Lotus
  module Routing
    class Builder
      def initialize(&blk)
        @router = Lotus::Router.new
        instance_eval &blk
      end

      def mount(app)
        @app = app.new
        @router.mount @app, at: "/"
      end

      def call(env)
        @router.call(env)
      end
    end
  end
end

# config.ru
run Lotus::Routing::Builder.new {
  mount Frontend::Application
  mount Backend::Application
}

As you can see, all application are mounted at the same point ("/"), what's (clearly) a problem, because http_router will recognize request coming to the first registered application. In other words, my solution does not work.

I'm still trying to figure out which approach can solve this problem, I mean, which alternative I have to do achieve first step solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants