zendframework / zend-expressive

PSR-15 middleware in minutes!
BSD 3-Clause "New" or "Revised" License
711 stars 197 forks source link

[Cookbook] Config best practices #155

Closed bakura10 closed 7 years ago

bakura10 commented 9 years ago

Hi,

We should add a note about what would be the best way to organize config files at scale (with .local/.global explanation, if config files should better be split by concerns - routes / services / ... - or by middleware/modules)... and so on.

It's a bit confusing, because of the lack of modules and merging of config (as of today).

geerteltink commented 9 years ago

There is some info here to get you started: https://github.com/zendframework/zend-expressive-skeleton/blob/master/config/config.php.

I have no idea what the best practices are. But from what I got from the discussion we had a while ago, configuration keys should be prefixed with a package name where possible.

Personally I like to split some routes config files to organize the admin, api and frontend routes. As long as you keep in mind that .global is loaded first and .local will overwrite those keys, I guess it comes down more to personal preference.

Besides that I tend to have all container dependencies in the dependencies.global.php file. However there is an exception for whoops, since that one should have be .local as you don't want that one on your production site. I can imagine people like to have the template container dependencies in the template.global.php file. Again, personal preference I guess.

bakura10 commented 9 years ago

I think there is a small error here: https://github.com/zendframework/zend-expressive-skeleton/blob/master/public/index.php#L14 (the config/container does not eixst in your repo).

Actualy, I was more referring about external libraries/middlewares that have their own config (adn that you need to copy paste into your app as those middleware/library can't bring their own config automatically yet).

I know it's preference but by encouraging people to do things a given way we can have a more coherent ecosystem in the long run :).

kynx commented 9 years ago

@xtreamwayz Do you have your admin/api/frontend as a single ZFE\Application? I split mine out into separate middlewares, each with it's own app. That way you can load the routes from routes_admin, routes_api keys in the config. No idea if loading less routes is any real performance advantage, but it makes them easier to find.

For config, don't claim it's right, but here's what I'm doing:

config/
  application/
    config.(global|testing|dev|local).php
    routes.(global|testing|dev|local).php
    templates.(global|testing|dev|local).php
    wiring.(global|testing|dev|local).php
  config.php
  environment.php
  README.md
  services.php

services.php includes environment.php (error_reporting et al) then checks cache and includes config.php if needed. That merges everything under application.

I dunno - it suits me. The bits the twits shouldn't mess with are bottom of the list.

So for other stuff something similar, with a vendor folder composer can drop files in? But then composer would need to know it was in a ZFE app and.... darn... is there a PSR in this?

;-)

samsonasik commented 9 years ago

@bakura10 config/container.php is written on installation process when run composer create project.

geerteltink commented 9 years ago

@kynx It's 1 app at the moment. I've got an early version online if you are interested: https://github.com/xtreamwayz/zend-expressive-app-poc

@bakura10 Ah right, I get it now. I've been thinking about that after the other discussion here. What you could do is have something like config-interop or you design a better / improved standard. And then in the middleware module you mark in the composer file the package as middleware-module or zend-expressive-module. You create a plugin for composer that will be triggered when something tagged as a middleware module and it copies the configuration to the config/autoload folder.

I mentioned config-interop, but you don't need something that complicated. Just setup some naming rules and you are done.

File should be named <package-name>.global.php or <package-name>.local.php (for local / debug packages and sensitive info).

return [
    // container settings
    'dependencies' => [
        'aliases' => [
            // ...
        ],
        'factories' => [
            // ...
        ],
    ],

    // package related settings
    '<package-name>' => [
        'key' => 'value',
    ],

    // zend-expressive settings
    'zend-expressive' => [
        'error_handler' => [
            'template_404'   => 'error::404',
            'template_error' => 'error::error',
        ],
    ],

    // app specific settings
    'app' => [
        'debug' => false,
        'config_cache_enabled' => false,
    ],
];

Place the <package-name>.php files in package/config and the component/installer could copy the config file to the applications config/autoload file. During copying config files it should delete and cached config files.

Once the composer/installer understands the zend-expressive-middleware package, all you need to do is this to activate it and do it's work.

codeliner commented 9 years ago

@xtreamwayz

This is not enough!

    // package related settings
    '<package-name>' => [
        'key' => 'value',
    ],

Packages of different vendors can have the same package name. The convention should really be:

    // vendor package related settings
    '<vendor-name>' => [
        '<package-name>' => [
            'key' => 'value',
        ],
    ],

Can't understand why you say config-interop is complicated. It just defines such a standard for configuration and provides some really good helpers to create factories dealing with config. The helpers avoid writing lot of boilerplate! But you can of course write it yourself over and over again and waste time with writing and TESTING factory logic.

geerteltink commented 9 years ago

@codeliner Right, forgot about the vendor name.

I didn't mean to say that config-interop is complicated, just that it adds unnecessary difficulty. I haven't used the config-interop yet but I've looked into the code and it seems that it adds checks and throws exceptions if some config is not there. If I just get $config['vendor-name']['opackage-name']['key'] it also throws an error. Yeah, it's not so fancy but it makes the user check the default config for changes.

But like I said before, I'm not a config pro and I haven't published any open source modules. I might actually need to have another look at config-interop.

codeliner commented 9 years ago
$config = [];

$key = $config['vendor-name']['opackage-name']['key'];

//Notice: Undefined index: vendor-name

This piece of code would never pass my code review as I don't want to have such things in production code. However, for this discussion only the convention part of interop-config is required so I agree that you don't need to have a real composer dependency of interop-config to define a config convention. And as you agreed that <vendor-name> should be in you actually agreed with the interop-config convention ;)

In the last run we ported all ZF2 factories of our components to container-factories and currently we are working on interop-config integration to allow users to use our factories but define their own scopes. The traits and interfaces provided by interop-config simplify the logic of abstract factories a lot and/or can replace them. But yeah, that is more interesting when working on open source components / modules but also when writing factories for your application services and whatever.

sandrokeil commented 9 years ago

@xtreamwayz I've added a quick start guide to the interop-config library. With the new naming it should be easier to understand an I also reduced the complexity of this library.

:+1: for @codeliner suggestions which follows the interop-config specification.

We should distinguish between factory configuration (libraries) and application configuration. In your example app specification settings must not follow the interop-config specification in this case but should be under the app key to avoid conflicts.

sandrokeil commented 8 years ago

@xtreamwayz

I've looked into the code and it seems that it adds checks and throws exceptions if some config is not there. If I just get $config['vendor-name']['opackage-name']['key'] it also throws an error.

Since interop-config 0.3.0 you have a method canRetrieveOptions which only checks if config can be retrieved and throws not an exception.

@kynx Your config structure with files local, testing, global ... is ok, but it's not a best practice. It is good for beginners but I recommend to use the 12factor app principles. Environment variables like DB connections for dev, staging, testing ... should not be part as a file of the repo. It does not scale and contains sensitive information. Enterprise projects should have a build process which handles such a stuff. DB connections for example should be part of the server env variables or should be discovered via a (web) service. If you changed the DB connection you have to deploy your app in a new version, that is not neccessary and wrong. Imagine you have one package (zip) which you can deploy to staging, testing and production without rebuilding it.

/cc @bakura10

kynx commented 8 years ago

@sandrokeil Thanks for the link - looks interesting.

Yeah, sensitive stuff isn't checked in. Regarding using environment variables - they'll still need to be stored somewhere won't they? If I'm going to worry about scaling I'll be using something like ansible or chef to manage configuration / orchestration, which makes it less of an issue. And hey, compared to how I've seen Java apps scatter sensitive configuration around anything is better :wink: