Dependency Injection: How to switch API server?

I often make a module named Request.elm which collects all API that communicate with server.

getUser : UserId -> Request User
postUser : User -> Request User
deleteUser : UserId -> Request ()

The URL is constructed in each function.

url = "/user/" ++ toString userId

At some point, it requires extra information about which server (domain or path) to access.

url = root ++ "/user/" ++ toString userId

That means I need to modify all functions like this.

getUser : String -> UserId -> Request User
postUser : String -> User -> Request User
deleteUser : String -> UserId -> Request ()

Now I have 50+ functions and want to avoid modifying all of them.
Any idea to solve this?

  • Simply write them all
  • Manually change the Elm code every time
  • sed before compiling
  • Modify /etc/hosts for development
  • Hook requests using JS (Is it possible?)
  • New Elm features…? (Allow static value for ports, introduce parameterized modules, etc.)

Also, I have similar problem on generating i18n messages.

hello : Language -> String

How do you think? Is there any good practice?

A technique I like:

Make a directory called env that’s a sibling of src. Put env in .gitignore and also in source-directories.

Inside that directory, put things like modules containing environment-specific paths, third-party API credentials, etc. All they should do is expose constants like strings.

Other modules can import and access values from these as normal. Then you just need a system for distributing different versions of the env modules to team members for local development and the various servers that ought to have different values, and you’re all set!

If any of them ever gets out of sync (i.e. you add a value in one environment and forget to add it in one of the others) the code won’t compile!

5 Likes

@rtfeldman
Oh, that seems cleaner than any of the options I listed above!
Will try it. Thanks for the rapid response :smile:

1 Like

Though it only works for API example, I prefer the way to change API root by reverse proxy rather than changing Elm code itself.

@arowM
Does that work when API server is located at different domain?

Partly yes, but a bit complicated than same domain.

Some reverse proxies can check the referrer of an access and pass it to proper path.
At least, when we use different domain, it should be checking referrer to realize CORS access.
The reason “partly” is because it cannot pass to your local development server if you does not publish your machine to the Internet.

From the above, I recommend the way using reverse proxy if your project uses (or can migrate to use) same domain to serve web API, and if not, using complicated way of reverse proxying or some way like rtfeldman tells.

Here is how I am setting things up to deploy to different environments.

I define a Config module which specifies all the parameters of the environment:

type alias Config =
    { applicationContextRoot : String
    , apiRoot : String           -- The main API being consumed.
    , avatarApiRoot : String     -- Another API.
    , ...                        -- More APIs...
    }

I also write a config decoder:

configDecoder : Decoder Config
configDecoder = ...

At the top-level I use programWithFlags, and pass in the config as a Value in the flags, and decode it to a Config.

The config parameters can be put in a .js file, under an environment specific directory, like env described by Richard, and set things up so that the index.html containing the Elm application can pick that up on a known relative path from the web server.

<script src="env/params.js"></script>

OR I set up the back-end server that serves up the index.html to generate that index.html dynamically, and inject the correct parameters.

It is worth getting used to the fact that APIs in development and APIs in test/production are going to be in different locations, so always write your API calls in this format:

getUser : String -> UserId -> Request User
postUser : String -> User -> Request User
deleteUser : String -> UserId -> Request ()

You can also easily write a default Config for development, that would supply “” for the API root locations.

1 Like

Normally I create two main entries to my app main = Html.programWithFlags { init = init ... } that is used in Main.elm for production and the reactor = Html.program { init = init defaultFlags, ... } that is exposed as main in a Reactor.elm. That way you have the debug settings in elm-reactor and you can use the normal entry for production, this has zero overhead at least for me as a single person developing this :slight_smile:

Thank you all.

@arowM
I basically assume that web pages and API are different domains. Sometimes they are same but sometimes not. So I’m looking for some solution independent from which the case will be. Also, it could be better if teammates can easily set up that without polluting their environments.

@rupert
Yeah, I tried that in my previous project. But the reason I started this thread is that I don’t like to do that again :sweat_smile:

@mfeineis
The problem that I wanted to discuss was that “how to write less code to switch the behavior”. So I can pass the flags from the top but have to pass it hundreds of times.

1 Like

In Ellie I write a variable with a string value that looks like "%API_BASE%" and then I use a string replacement plugin in my build to replace that string with whatever is in the process env

5 Likes

How about having a default file in git (for developers), but allowing others (sys-ops) to override it for production?

This works too, but has the downside that if you misconfigure it, instead of getting an error, your end users will just get the development version. :sweat_smile:

@luke
I just saw your code and tried that. Seems working fine. Thank you! :smiley:

1 Like