Core Concepts
• flamingo.App
• Dingo
• Config
• Routing
Flamingo Bootstrap
Bootstrap
• Define the configuration Areas

• Load default configuration

• Load the current configuration and routing information

• Load overriding configuration

• Register additional Handlers

• Run the main command / Start a router
flamingo.App
flamingo.App
• Main entry point, starts the Flamingo instance

• Configures dingo dependency injection and runs the root
command
flamingo.App / Changes
• flamingo.App(..., flamingo.AppAutorun(false))

• Allows external control of the App lifecycle
Dingo Modules
Dingo
• Flamingo Dependency Injection

• Interface similar to Guice

• Supports Singleton, Multi-/Mapbindings
Dingo
type HelloController struct {
responder *web.Responder
}
func (c *HelloController) Inject(responder *web.Responder) {
c.responder = responder
}
General
• Bind dependencies on application startup (in code)

• (future support for compile-type bindings)

• Config-Tree based injector-trees: It's possible to inject
different services/adapters for certain configuration areas
Binding
• General Syntax:

injector.Bind(new(Iface)).To(IfaceImpl{})
• .To(type) binds to a type

• .ToInstance(instance) binds to an instance

• .ToProvider(func() instance) binds to a provider
Annotation
• Default annotation is empty

injector.Bind(new(Iface)).AnnotatedWith("special-case").To(IfaceImpl{})
• Inject via

func (instance *MyType) Inject(annotated *struct{
IfaceInstance Iface `inject:"special-case"`
}) {
instance.iface = annotated.IfaceInstance
}
• Used by configuration for configuration values
Singleton Scopes
• If really necessary it is possible to use singletons

• .AsEagerSingleton() binds as a singleton, and loads it
when the application is initialized

• .In(dingo.Singleton) makes it a global singleton

• .In(dingo.ChildSingleton) makes it a singleton limited to
the config area
MultiBindings
• Allows to bind multiple instances/providers/types for one
type
func (*Module) Configure(injector *dingo.Injector) {
injector.BindMulti(new(Iface)).To(IfaceImpl1{})
injector.BindMulti(new(Iface)).To(IfaceImpl2{})
}
func (*MyService) Inject(ifaces []Iface) {}
MapBindings
• Similiar to Multibindings, but with a key instead of a list
func (*Module) Configure(injector *dingo.Injector) {
injector.BindMap(new(Iface), "key1").To(IfaceImpl1{})
injector.BindMap(new(Iface), "key2").To(IfaceImpl2{})
}
func (*MyService) Inject(ifaces map[string]Iface) {}
Provider
• If you need to make sure bindings are lazy evaluated, or
need multiple instances of a certain type, it is possible to
inject provider

• Define a type which ends with Provider, and gives you
what you need:
type IfaceProvider func() Iface
func (*Service) Inject(provider IfaceProvider) {
ifaceInstance := provider()
}
Provider
• It is possible to get provider for map/multibindings

• It is possible to get provider for provider

• Use with care: this might be an indicator of too complicated
code
Configuration
config.Map
• config.Map is a container for string -> interface{}

• configuration is available via annotated injection

• MyConfig string inject:"config:my.config"
• My config.Map `inject:"config:my"`
• Numbers: int is also float64
Config Merging
• Dot . separated config keys

• Every . donates one level in the tree:
my.config.value: hello
my:
config:
value: hello
Config Merging
• Config Maps are deep merged

• Allows overriding sub-keys:
my:
foo: bar
config:
value: hello
value2: world
my.config.value: myhello
Config Loading
• YAML Loader (others might follow)

• ENV Substitution via %%ENV:something%%default%
%

• config.yml

• config_$CONTEXT.yml

• config_local.yml

• $CONTEXTFILE
Configuration Areas
Configuration Areas
• Flamingo Config is hierarchical

• Config Areas can have multiple children, they inherit
config and dependency injection bindings

• Config is loaded based on file-system layout
Config
Upcoming Changes
• Cuelang support

• Allows Schema validation and much more
Default Configuration
• Modules in Flamingo can provide a default config, and
override existing config during the bootstrap

• The default configuration is loaded before the config
folder

• The override configuration can manipulate configuration
afterwards (use with care!)
Default Configuration
func (m *Module) DefaultConfig() config.Map {
return config.Map{
"my.config.test": "default value",
}
}
Overridden Configuration
func (m *Module) OverrideConfig(current config.Map) config.Map {
if oldvalue, ok := current.Get("my.config.test"); ok {
log.Println("Overriding", oldvalue)
} else {
log.Println("No old value")
}
return config.Map{
"my.config.test": "overriden value",
}
}
Routing
Routing: Paths
• Named Parameters: /route/:name

• Match everything until the following /

• Regex Parameters: /route/$name<[a-z]{2,}>

• Match everything which is captured by the regex (if
possible)

• Wildcard Parameters: /route/*name

• Match everything (everything until the end of the route)
Routing: Controller
• Map a route to a controller, either in routes.yml or in code

• my.controller

• Gets all URL parameters

• my.controller(name="foo")

• Sets name to foo

• my.controller(name?="foo")

• Sets name to foo if not set by a GET parameter

• my.controller(name?)

• Sets name to the value of the name GET parameter, if available
Routing: Reverse
• Reverse URLs are build based on available routes

• web.ReverseRouter

• Newest routes take precendence

• Router parameters are taken into account, meaning:

• /home -> cms.view(page="home")

• url("cms.view", router.P{"page": "home"}) -> /home
web.RouterRegistry
• HandleGet(context.Context, *web.Request) web.Result for
GET

• HandlePost(context.Context, *web.Request) web.Result for
POST

• Same for HEAD, DELETE, PUT

• HandleAny

• HandleData(context.Context, *web.Request) interface{} for
internal data requests
web.RouterRegistry
type routes struct {
controller *controller
}
func (r *routes) Inject(controller *controller) {
r.controller = controller
}
func (r *routes) Routes(registry *web.RouterRegistry) {
registry.Route("/", "home")
registry.HandleAny("home", r.controller.action)
}
core.Prefixrouter
• The core.Prefixrouter module provides a HTTP Router
which routes requests based on hostname/path

• When starting Flamingo the module will create HTTP
listener for all config Areas with a prefixrouter.baseurl
configuration
core.Cmd Package
core.Cmd
• Based on spf13/cobra

• Inject *cobra.Command with annotation flamingo to get
the main command

• Bind to cobra.Command to add custom commands

Flamingo Core Concepts

  • 1.
  • 2.
  • 3.
  • 4.
    Bootstrap • Define theconfiguration Areas • Load default configuration • Load the current configuration and routing information • Load overriding configuration • Register additional Handlers • Run the main command / Start a router
  • 5.
  • 6.
    flamingo.App • Main entrypoint, starts the Flamingo instance • Configures dingo dependency injection and runs the root command
  • 7.
    flamingo.App / Changes •flamingo.App(..., flamingo.AppAutorun(false)) • Allows external control of the App lifecycle
  • 8.
  • 9.
    Dingo • Flamingo DependencyInjection • Interface similar to Guice • Supports Singleton, Multi-/Mapbindings
  • 10.
    Dingo type HelloController struct{ responder *web.Responder } func (c *HelloController) Inject(responder *web.Responder) { c.responder = responder }
  • 11.
    General • Bind dependencieson application startup (in code) • (future support for compile-type bindings) • Config-Tree based injector-trees: It's possible to inject different services/adapters for certain configuration areas
  • 12.
    Binding • General Syntax: injector.Bind(new(Iface)).To(IfaceImpl{}) •.To(type) binds to a type • .ToInstance(instance) binds to an instance • .ToProvider(func() instance) binds to a provider
  • 13.
    Annotation • Default annotationis empty injector.Bind(new(Iface)).AnnotatedWith("special-case").To(IfaceImpl{}) • Inject via func (instance *MyType) Inject(annotated *struct{ IfaceInstance Iface `inject:"special-case"` }) { instance.iface = annotated.IfaceInstance } • Used by configuration for configuration values
  • 14.
    Singleton Scopes • Ifreally necessary it is possible to use singletons • .AsEagerSingleton() binds as a singleton, and loads it when the application is initialized • .In(dingo.Singleton) makes it a global singleton • .In(dingo.ChildSingleton) makes it a singleton limited to the config area
  • 15.
    MultiBindings • Allows tobind multiple instances/providers/types for one type func (*Module) Configure(injector *dingo.Injector) { injector.BindMulti(new(Iface)).To(IfaceImpl1{}) injector.BindMulti(new(Iface)).To(IfaceImpl2{}) } func (*MyService) Inject(ifaces []Iface) {}
  • 16.
    MapBindings • Similiar toMultibindings, but with a key instead of a list func (*Module) Configure(injector *dingo.Injector) { injector.BindMap(new(Iface), "key1").To(IfaceImpl1{}) injector.BindMap(new(Iface), "key2").To(IfaceImpl2{}) } func (*MyService) Inject(ifaces map[string]Iface) {}
  • 17.
    Provider • If youneed to make sure bindings are lazy evaluated, or need multiple instances of a certain type, it is possible to inject provider • Define a type which ends with Provider, and gives you what you need: type IfaceProvider func() Iface func (*Service) Inject(provider IfaceProvider) { ifaceInstance := provider() }
  • 18.
    Provider • It ispossible to get provider for map/multibindings • It is possible to get provider for provider • Use with care: this might be an indicator of too complicated code
  • 19.
  • 20.
    config.Map • config.Map isa container for string -> interface{} • configuration is available via annotated injection • MyConfig string inject:"config:my.config" • My config.Map `inject:"config:my"` • Numbers: int is also float64
  • 21.
    Config Merging • Dot. separated config keys • Every . donates one level in the tree: my.config.value: hello my: config: value: hello
  • 22.
    Config Merging • ConfigMaps are deep merged • Allows overriding sub-keys: my: foo: bar config: value: hello value2: world my.config.value: myhello
  • 23.
    Config Loading • YAMLLoader (others might follow) • ENV Substitution via %%ENV:something%%default% % • config.yml • config_$CONTEXT.yml • config_local.yml • $CONTEXTFILE
  • 24.
  • 25.
    Configuration Areas • FlamingoConfig is hierarchical • Config Areas can have multiple children, they inherit config and dependency injection bindings • Config is loaded based on file-system layout
  • 26.
    Config Upcoming Changes • Cuelangsupport • Allows Schema validation and much more
  • 27.
    Default Configuration • Modulesin Flamingo can provide a default config, and override existing config during the bootstrap • The default configuration is loaded before the config folder • The override configuration can manipulate configuration afterwards (use with care!)
  • 28.
    Default Configuration func (m*Module) DefaultConfig() config.Map { return config.Map{ "my.config.test": "default value", } }
  • 29.
    Overridden Configuration func (m*Module) OverrideConfig(current config.Map) config.Map { if oldvalue, ok := current.Get("my.config.test"); ok { log.Println("Overriding", oldvalue) } else { log.Println("No old value") } return config.Map{ "my.config.test": "overriden value", } }
  • 30.
  • 31.
    Routing: Paths • NamedParameters: /route/:name • Match everything until the following / • Regex Parameters: /route/$name<[a-z]{2,}> • Match everything which is captured by the regex (if possible) • Wildcard Parameters: /route/*name • Match everything (everything until the end of the route)
  • 32.
    Routing: Controller • Mapa route to a controller, either in routes.yml or in code • my.controller • Gets all URL parameters • my.controller(name="foo") • Sets name to foo • my.controller(name?="foo") • Sets name to foo if not set by a GET parameter • my.controller(name?) • Sets name to the value of the name GET parameter, if available
  • 33.
    Routing: Reverse • ReverseURLs are build based on available routes • web.ReverseRouter • Newest routes take precendence • Router parameters are taken into account, meaning: • /home -> cms.view(page="home") • url("cms.view", router.P{"page": "home"}) -> /home
  • 34.
    web.RouterRegistry • HandleGet(context.Context, *web.Request)web.Result for GET • HandlePost(context.Context, *web.Request) web.Result for POST • Same for HEAD, DELETE, PUT • HandleAny • HandleData(context.Context, *web.Request) interface{} for internal data requests
  • 35.
    web.RouterRegistry type routes struct{ controller *controller } func (r *routes) Inject(controller *controller) { r.controller = controller } func (r *routes) Routes(registry *web.RouterRegistry) { registry.Route("/", "home") registry.HandleAny("home", r.controller.action) }
  • 36.
    core.Prefixrouter • The core.Prefixroutermodule provides a HTTP Router which routes requests based on hostname/path • When starting Flamingo the module will create HTTP listener for all config Areas with a prefixrouter.baseurl configuration
  • 37.
  • 38.
    core.Cmd • Based onspf13/cobra • Inject *cobra.Command with annotation flamingo to get the main command • Bind to cobra.Command to add custom commands