erlware / relx

Sane, simple release creation for Erlang
http://erlware.github.io/relx
Apache License 2.0
697 stars 232 forks source link

include app's erl without start the app #639

Closed GeraldXv closed 5 years ago

GeraldXv commented 6 years ago

I got an app called db_svr, and another app called center_svr. db_svr needs to call the center_cli:init, which is actually in the center_svr. so I write this in rebar3.config: {release, {db_svr, "0.1.0" }, [{center_svr, load}, {share, load}, db_svr], ... }, I found the center_svr is not started, but the deps app of center_svr were started. like lager, poolboy in the center_svr.app.src. Any idea that can include the center_svr code but not start poolboy?

ferd commented 6 years ago

You'll have to specify all the deps you don'T want to start automatically individually. By default (and unless specified otherwise) all the apps are just started.

I don't think there is a case where we can safely assume that a loaded app wants its deps to only be loaded rather than started.

GeraldXv commented 6 years ago

I throught load means include the code of other apps, maybe I was wrong. As I am using the umbrella structure of rebar3, any other configure that I can use to call module of app1 in app2?

ferd commented 6 years ago

Relx should automatically load all dependencies of your application, in fact it will start them by default as long as they're in the .app file's applications tuple.

GeraldXv commented 6 years ago

For 3rd deps, it is yes, relx load all beams. In my issue, I want to call functions of app2 in app1. Both app1 and app2 is one of my project as I am using the "umbrella" structure of rebar3. I found it is hard to do it. I wish I had a clear expression.

GeraldXv commented 6 years ago

Attached is the tree view of my project:

apps:
├── cache_worker
│   ├── include
│   ├── README.md
│   ├── rebar.config
│   └── src
│       ├── cache_cli.erl
│       ├── cache_worker_app.erl
│       ├── cache_worker.app.src
│       ├── cache_worker.erl
│       └── cache_worker_sup.erl
├── game_svr
....

game_svr needs to use the cache_cli but do not need to start the cache_worker, as cache_worker is started in another node (deployed as a service).

  {release,
    {game_svr, "0.1.0" },
    [game_svr,  {share, load},..{cache_mgr, load}]
    }

This works in my project but the applications configured in the cache_worker.app.src will stared automatically also. I don't want to add the applications with a "load" in the rebar.config one by one.

bitwalker commented 6 years ago

The problem is that cache_cli may make use of applications cache_worker depends on, and those applications may need to be started in order to work properly. There is no way to know this when including it in the release, even if cache_worker is given a start type of load, because start types are not transitively applied, and can't be, since as you show here, you can still call functions in a loaded application even if it's not started.

What you want makes sense in your specific context, because you know the module you are calling doesn't require running dependencies (I assume), but it doesn't make sense in the general case. The only option here would be for relx to provide a pseudo start type like load_transitive or something, and then automatically apply the load start type to any dependencies which are unique to that application. I don't think that's a good idea though, because it is far too likely to result in unintentionally broken releases.

Is there a specific reason why starting those dependencies is a problem? I would think libraries should be mostly idle without a controlling application telling them to do something actionable, at least all of the ones I can think of operate that way. If it's really a concern, then factoring cache_cli into its own application may make sense rather than this approach.

GeraldXv commented 6 years ago

Thanks, @bitwalker .

Is there a specific reason why starting those dependencies is a problem?

Yes, as the cache_worker deps on a application "redis_pool" (configured in the sys.config) which will connect to our redis service. While the cache_cli do not need to know the configures of redis. And the cache_cli will be used by many apps in our project like game_svr and cdkey_svr. Maybe there is two ways to solve my issue:

  1. start the applicionts in the cache_worker_app.erl but not the cache_worker.app.src.
    1. make cache_cli a stand alone lib project.
bitwalker commented 6 years ago

If it were my project I would almost certainly take what's behind door 2, since it sounds like it's intended to be mostly standalone anyway.

For dependencies like redis_pool, I prefer that things like database connections and the like be started as part of the controlling applications supervisor tree, not the supervisor tree of the dependency - that should be reserved for internal processes which must be managed by the dependency necessarily. Anything user configured should generally be managed in the users application. If you are able to make those changes, I would.

Hope that answers your question!

GeraldXv commented 6 years ago

Thanks, @bitwalker . I made some modification according to the way you said about redis_pool. By the way, where would you start applications like lager, app.src or app.erl? As lager needs some custom configuration sometimes.

ferd commented 6 years ago

put the custom configuration in sys.config and start like other apps in a release, IMO

bitwalker commented 6 years ago

@GeraldXv In the case of lager, I agree with @ferd - assuming the configuration is static (i.e. doesn't depend on information only known at runtime). If you are working an application which needs to be configured conditionally based on runtime information (say you need to pull config from the environment or something like etcd), then you will want to either use the approach I described for redis_pool, allowing you to construct the config prior to starting your application.

If it's not an application you control, but some dependency that isn't designed to let you start components as part of your supervision tree, then an alternative approach is to include the application (i.e. use included_applications rather than applications) and start the application under your supervisor tree (using app:start/2 as the start function for the child spec), which will allow you to configure it before starting it. That's the approach I've used when I hit dependencies like that. In general I try to avoid libraries which don't hand control over to my application and require runtime configuration, in my opinion it reflects poor design, but sometimes your hands are tied.

In Elixir, we're making a push towards a more standardized approach to runtime configuration, where applications that need configuration at startup provide an init callback taking a module/function pair, which is called in that applications start/2 callback via apply/3, and allows you to do whatever you need to do to fetch/load/transform configuration before it is required by that application. Elixir has a highly dynamic config system via Mix, but it currently doesn't play nice with releases, as we're converting the config to sys.config when building the release. That will likely change in the future, but it's the primary problem driving the conversation around configuration right now. Phoenix is a big one that has taken the init approach recently, and it's what I've been recommending to anybody designing new libraries. This doesn't have to be unique to Elixir obviously, but I don't know what the current recommendation is for library authors in the Erlang community. To be clear this approach is intended to fill the gap between libraries which allow you to start components as part of your supervisor tree (for example, like how cowboy lets you start the server as part of your application), where you can do configuration easily beforehand, and libraries where the internals require configuration which is needed at application start, where you don't have that opportunity. If you don't fall into the latter category, the init callback isn't necessary.

GeraldXv commented 6 years ago

This make sense, thanks for your help. @bitwalker @ferd