You’re right. I didn’t think about that before posting. Using the application env is probably the way to go in that case. But even for your own code I think it’s good to push the need for global state as far out as possible and reasonable. The possibility for sometimes needing to run two different configurations side by side exists for both your own code as well as for libraries.
Great to know that.
Great to hear.
I am new to Elixir/Phoenix and worked through “Programming Phoenix” (the old version). Now I want to deploy a very simple Phoenix app somewhere (hopefully not too expensive, but not on Heroku as I want to be able to use all Erlang/Elixir power in future) just to see if I can suceed in this before I continue learning Elixir/Phoenix and develop my first web application.
The last few days I searched the web for tutorials/guides an how to deploy Phoenix in production. As I use a Windows machine I have not found anything that gave me enough hope to try it. My skills are very low - all I have done so far is putting tiny Rails apps on Heroku…
Now I read that you plan to write a guide for Windows. Do you mean Windows running on the development or production side?
You should be passing in configuration required by library code as parameters, i.e. like you would any other input to a function. If it is compile-time configuration, then using app env for that is fine, since it is static - but for dynamic configuration, you should think of that config like you would any other input.
You don’t need a GenServer or other process dedicated to configuration - in an Elixir application, you will always have an entry point which is an OTP process; if you don’t, then it is because the application is a library, and the application calling that library becomes responsible for providing the configuration.
Does that better answer your question?
I plan to write a guide for running on Windows which covers both development and deployment to production Windows machines, since there is no benefit to using the Windows toolchain if you are deploying to Linux (you are better off using Windows Subsystem for Linux in that case, or running a VM/Docker container). There are some necessary changes required to the Windows tooling though before that guide will be fully ready.
No, it doesn’t really answer the question. To clarify, I’m not talking about Libraries, but about application code that requires configuration. For example, a Phoenix web application. Yes, my application will get initialized but most of my code isn’t called from there; it is invoked by Plug/Phoenix. How do my controllers and context modules get access to configuration data that is known to my application only at boot time?
Thanks for clarifying, I think I misunderstood what you were asking previously.
You would use app env to store the configuration, but the overall scheme remains more or less the same - rather than pushing config down the supervisor tree (which you would still do for the non-request related parts of your application), you would fetch the config from the app env at the boundary of the system (e.g. via a plug), and add configuration to the context which is passed along with the request.
When you reach a point where you need to do some work outside of the request context, you attach the configuration to the context used for that work. This is pattern is fairly common. For example, it is the recommended approach for such things in Go, via Context - which is an abstraction that works beyond just configuration (it can be used for tracing in particular, amongst other things).
So to restate things - you can use the application env for storage, that is it’s purpose; but you shouldn’t be using it like a context. Push config into app env during boot via a config provider, or some other means; then pass the configuration structure down your supervisor tree, or fetch it at the boundaries of your system, and pass it along in a dedicated context structure; then reach into that structure for specific values - but don’t sprinkle
Application.get_env/3 all over your code base.
Hopefully that better answers your question, but definitely let me know if there are still questions or parts lacking clarity!
Thanks, that does clarify it quite a bit. I agree with this but thought there was maybe another mechanism I’d overlooked.
Fetching and pushing configuration down the tree has one disadvantage on dealing with reconfiguration, where you need to implement for every configuration mechanism for reconfiguration.
With application environments, changes are propagated automatically, if fetched every time via
Application.get_env, otherwise there is application callback to use
config_change, where you can make call/send messages to a processes to reconfigure, restart processes(if listening port changes) and so on.
Runtime reconfiguration is important topic by dealing with configuration and it works for application environment (
Application.get_env or config_change application callback) and a question how it should work with propagating configuration to the tree? Implement somehow change_config by every process? Implement
config_change in application?
I can’t think about dealing with configuration, without dealing with runtime reconfiguration, and
Application.get_env direct call is the simple way to deal with it and may be should used, where it still make sense?. And
config_change allows to implement it, where processes save configuration in a state(or for example bind on another port).
I think there are two stages of propagation - that which happens at boot - which is only for configuration that will never change during runtime. And propagation per request, where the state is fetched for each request, so will always be current. For example a plug putting config data into the
Cases that are neither of these would need to directly use
config_change I think.
Example, log level, it happens at boot, but you want to reconfigure it without restarting of application. So, they are not always different things. It happens at boot time, but still can be reconfigured. And we got, that Logger application reinvented application environments with own GenServer and ETS table.
I would say, that most configuration of application(which is not compile-time configuration) should be defined at boot and where possible reconfigurable in runtime, like log level, it is just a perfect example. It is my personal opinion: that’s is best for user experience, what you can do.
So I deploy to Windows… basically tried to upgrade from exrm to distillery today, the cookie part (I think) doesn’t work, my server starts C nodes and they weren’t able to talk to each other. Went back to exrm and everything just worked again. I know exrm is not supported but it kinda just works.
So I deploy to Windows…
Windows support is being rewritten right now in Distillery because most of the functionality available on *Nix is unsupported on Windows due to the difficulty of porting that functionality to batch script. While Distillery 2.0 did work on Windows during the RC period, last minute changes to the way config providers worked were not propagated to the Windows scripts because I’m in the middle of that rewrite (basically porting all of the bash scripts to Powershell, rather than porting the batch scripts, since they are horrendous).
Also, reporting issues is important feedback - there is no guarantee I will see a message like yours here on this forum, or elsewhere for that matter. If you want to make sure issues get fixed, reporting issues is critical.
For runtime reconfiguration of anything that impacts the state of running processes, it is expected that you would use the
config_change handler in the application callback module - when this is invoked it ensures that you can execute configuration changes in a predictable and coordinated fashion.
The problem with just hitting
Application.get_env/3 everywhere when it comes to reconfiguration, is that you can’t guarantee anything about when you will fetch values from there. For example, you may fetch two values from
Application.get_env/3 in the same function, both of which depend or are related to each other in some way - if configuration changes are being applied while that function is executing, you may get a mix of old and new values, which result in odd or unexpected behavior. By instead coordinating the config changes with the processes which need to change, you get predictable results, and can make sure that trickier config changes, such as changing the port a socket is listening on, etc., are performed correctly.
For something like your Logger example, yes, fetching directly from the application env may work just fine, and may even be preferable if it is something you need to do often - but cases where you can safely reconfigure things on the fly like that without any guarantees are less common, I think, than cases where the configuration has impacts on the state of a process, or many processes, or may even change the behavior of the application entirely (such as changing the implementation module for some behavior).
You don’t have to implement config change handling in every process, you implement it in the application callback module, and orchestrate the configuration changes from there. This may mean talking to processes when they need to perform some work to reconfigure themselves, it may mean starting new children under a supervisor and terminating old ones, or it may not require any implementation work at all. For convenience, it may make sense to have
config_change functions defined at the supervisor or process level, and have the application
config_change function call those functions, but it’s certainly not required.
Yes, I’m not arguing that you should never use
Application.get_env/3, there are still times where that is useful or convenient - but you should prefer configuration by parameters where possible, especially in libraries, because you don’t know if
Application.get_env is going to be convenient for your users. This is a huge part of why runtime configuration has been such an issue with releases historically - libraries leaned too heavily on application env, rather than on designing APIs which are configurable, and it made it very difficult to configure them (naturally), as a result. Using application env is painful even outside of releases for similar reasons - it’s just another form of global state, which means that if you need to change that state, or run multiple configurations side-by-side, you can’t.
@bitwalker just a quick note: your GitHub
README.md points to ElixirForum section that no longer exists.
It’s a tag now #deployment so that threads about Elixir/Phoenix/Nerves deployment threads can be posted in their respective sections yet still be tracked easily
Even better. Just pointing it out for him to update the link.