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

Proposal: Implement Revel as a set of loosely coupled tools. #5

Closed
ghost opened this issue Nov 9, 2016 · 2 comments
Closed

Proposal: Implement Revel as a set of loosely coupled tools. #5

ghost opened this issue Nov 9, 2016 · 2 comments

Comments

@ghost
Copy link

ghost commented Nov 9, 2016

This is a discussion of #2 and #4.

Current Architecture

Earlier I illustrated the architecture of Revel Framework along with my
misunderstanding of what "convention" means
. To summarize what was said:

  • Revel Framework consists of two parts:
    • revel/cmd package implements a command line tool for:
      • bootstrapping your applications;
      • recompilation and hot reloading them;
      • generation of main.go file;
      • generation of reverse routes;
      • starting integration tests;
      • building applications;
      • packaging them.
    • revel package is responsible for everything else:
      • parsing of configuration files;
      • parsing of routes (on every start of the app);
      • execution of templates;
      • allocation and start of http.Server;
      • allocation and initialization of all kinds of parameters for:
        • Session
        • Cache
        • Flash
        • Validation
      • value binding;

Drawbacks

  • User app is tightly coupled with the revel package and if they don't like something
    about its implementation aspects they have to ask about making changes to the core code base.
    • With this approach it is not possible to:
      1. Stop embedding revel.Controller;
      2. Do not use revel.Result;
      3. Use custom configuration format of choice;
      4. Use alternative implementation of router;
      5. Replace template package;
  • Slow releases: large code base that cannot be broken without releasing the next version, the next version cannot be released till enough changes are made;
  • A lot of unnecessary for a user dependencies;
  • The framework is difficult to maintain: monolithic codebase, expected behaviour of its components is not defined:
    • when user embeds revel.Controller instead of *revel.Controller, it works but shows Error 500, we have to guess whether it is by design or a bug.

Mechanisms of Extendability

  • Startup hooks - implemented as a global revel.OnAppStart variable of type []func() (now a bit more complex than that to support ordering);
  • Interceptors - similar to Startup Hooks but for special Actions that can be started Before, ..., After regular Actions.
  • Filters - similar to Startup Hooks but work on a level of HTTP Handler function.
Drawbacks
  • Too many ways to define middleware: interceptor functions vs. interceptor methods vs. filters;
  • No way for a middleware to share something with controllers, for Session, Cache, Flash, Validation this is
    solved by manually adding respective fields
    to the revel.Controller{} struct.
    • I.e. for some new middleware CSRF that means that in order to share CSRFToken with actions of controllers
      there are a few options:
      1. Add one more field to the revel.Controller{} struct;
      2. Pass the value using some field that already exists:
        • c.Controller.Session, c.Controller.Flash (not always possible, e.g. I may want to pass int type or some struct; and requires an allocation of a map even if I don't need a session but just a single variable of scalar type).

Default Layout

  • app/
    • controllers/ - imports revel package;
    • routes/ - imports revel package. Automatically generated by revel/cmd and cannot be commited due to .gitignore;
    • tmp/ - entry point of the application,
      imports: revel, revel/testing, revel/modules/*, controllers, tests.
      Automatically generated by revel/cmd and cannot be commited due to .gitignore;
    • views/
  • conf/
    • app.conf - INI configuration file;
    • routes - a list of routes in a custom format inspired by Play Framework;
  • messages/
  • public/
  • tests/ - integration tests that can be run by revel/cmd.
Drawbacks
  • app/ directory is a rudiment of Play Framework 1 that was written in Java where it is a regular
    practice to have a lot of nested directories. Go projects may have a more flat structure;
  • app/tmp/ and app/routes/ are not part of user app (they are in .gitignore), thus builds are not reproducable and revel/cmd is required to build/start an app;
  • conf/routes is not type safe, validated at start time, changes at the stage when the project has already been compiled
    lead to unexpected result (Solutions TBD);
  • tests/ cannot be run without revel/cmd.

Sample Code

  • app/controllers/init.go
package controllers

import (
	"github.com/revel/revel"
)

func init() {
	revel.Filters = []revel.Filter{
		...
		MyCustomFilter(*revel.Controller, fc []revel.Filter) {
			// Do something.
			...
			fc[0](c, fc[1:])
		}
	}
}
  • app/controllers/app.go
package controllers

import (
	"github.com/revel/revel"
)

type App struct {
	*revel.Controller
}

func (c *App) Before() revel.Result {
	return nil
}

func (c *App) Index() revel.Result {
	return c.Render()
}

func (c *App) After() revel.Result {
	return nil
}

func init() {
	revel.InterceptMethod((&App{}).Before, revel.BEFORE)
	revel.InterceptMethod((&App{}).After, revel.AFTER)

	revel.OnAppStart(func() {
		// Initialize the app at start-up time.
	})
}

Proposal

Tools

Implement everything that Revel provides as a set of independent tools.
Every single one should be usable on its own:

  • harness/ - hot reloads user applications;
    • main.go - entry point of the tool so it can be used independently;
    • runner/ - package that implements some Handler interface;
  • bootstrap/ - generates a new application from a specified skeleton;
    • main.go - entry point of the tool so it can be used independently;
    • creator/ - package that implements some Handler interface;
  • handler/ - generates standard handler functions from Revel-like controllers;
    • ...
  • routes/ - generates type safe reverse routes;
    • ...
  • TBD

Turn revel package into a simple command that imports the tool packages and starts their Handler functions.

Generated Code

Generated code should not depend on revel or any other projects if it's not possible to replace
the dependency easily. The standard library should be relied on as much as possible.

New Definition of Action

Any method that returns a type implementing standard http.Handler interface
as a first argument is an Action.

func (c *App) Index(...) http.Handler {
	return c.Render()
}
New Definition of Controller

Any struct type that has actions is a controller.

// App is a controller.
type App struct {
}

func (c *App) Index(...) http.Handler {
	return c.Render()
}

// Smth isn't.
type Smth struct {
}
Extendability
Special Actions
  • Before is a special action that will be executed before every regular action.
  • After is an equivalent of Before but with a finally semantics meaning that it will be guaranteed
    to be started after every regular action.
Binding

Actions can bind not only built-in types (int, uint, string, float32, etc.) but also http.Request,
types implementing http.ResponseWriter interface, and any other types that have a special
Bind(url.Values) method (TBD).

That would allow us to use the Special Actions as Filters (in Revel's terminology):

func (c *App) Before(w http.ResponseWriter, r *http.Request) http.Handler {
	http.SetCookie(w, c.cookieByRequest(r))
	return nil
}
Embedding of Controllers
  • Proposed controllers/init.go
type Controller struct {
	... // Third party controllers are here.
	*MyCustomController
}

type MyCustomController struct {
}

func (c *MyCustomController) Before(page int) http.Handler {
	// Do something.
}
  • Proposed controllers/app.go
type App struct {
	*Controller
}

func (c *App) Before() http.Handler {
	...
}

func (c *App) Index() http.Handler {
	return c.Render()
}

func (c *App) After() http.Handler {
	...
}
Drawbacks
  • Use of anonymous embedding requires every Controller to have a unique name. Solutions TBD.
  • Controller auto allocation rules TBD.
Startup Hooks

Functions for running some controller's code after init() but before the app is started
are still needed. One of the options is to have a special Init function reserved for that:

func Init() {
}

Or alternatively, auto start any function that is:

  • exported;
  • returns an error.

Details TBD.

func SomeFunctionInControllersPkg() error {
	return nil
}
New Layout
  • assets/ - autogenerated assets;
    • handlers/ - handler functions generated from controllers;
    • routes/ - reverse routes;
  • config/
  • controllers/ - controllers are splitted into independent packages (TBD);
    • account/
    • profile/
    • smthelse/
  • routes/ - imports assets/handlers (TBD);
  • static/
  • views/
  • main.go - entry point of the application, imports net/http, routes, and starts web-server;
  • revel.ini - configuration of the project: how to build it, run, and package. If not presented,
    default settings (by convention) will be used.
Motivation
  • go run is everything that is needed for start of the project;
  • User does not depend on revel package, only on the standard library;
  • Every single aspect of the app is customizable;

Other ideas

More value for the end users
  • Bring support of Meteor.js style rpc-publish-subscribe and data synchronization out of the box;
  • WebAssembly is coming. Think over how Revel can be used for isomorphic
    development in Go (i.e. the same language for both client and server side).
@ghost
Copy link
Author

ghost commented Nov 9, 2016

@brendensoares Let me know what you think.

@ghost
Copy link
Author

ghost commented Nov 27, 2016

I'm moving forward.

Thanks everybody.

@ghost ghost closed this as completed Nov 27, 2016
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

0 participants