Connection URIs for db configuration
Most tools and libraries support connection URIs, a way to specify the connection parameters using a single string instead of N connection parameters.Some of the advantages: having a single value that fully identifies the connection to an external resource (which makes it easy to use in environment variables and to compare the full connection “identifier”), reduces the proliferation of configuration parameters, prevents bad practices like inheriting parameters across environments.
Connection URIs offer a simple, standardized way of configuring connections to external services and resources, with great support across libraries, languages, tools and frameworks. Instead of having to specify each individual detail of a connection (host, port, auth credentials, database name) using language-specific and resource-specific configuration objects or serialized values (e.g. a JSON-formatted string), we can use a single, simple string that fully defines the link between the two systems.
Moreover, these connection URIs can easily be stored as environment variables, which allows for easy and dynamic configuration changes without modifying the application code or container images. This makes it easier to manage configuration across different containers and environments, improving application portability and scalability.
Compare this:
config :app, :db,
host: "db-production.helloprima.co.uk",
port: 5432,
database: "stonehenge",
username: "stonehenge",
password: System.fetch_env!("STONEHENGE_DB_PASSWORD"),
pool_size: 100
# Environment variables:
# STONEHENGE_DB_PASSWORD=AbCdEfG
to this:
config :app, :db, uri: System.fetch_env!("STONEHENGE_DB_CONNECTION_URI")
# Environment variables:
# STONEHENGE_DB_CONNECTION_URI=postgres://stonehenge:AbCdEfG@db-production.helloprima.co.uk:5432/stonehenge?pool_size=100
In the first approach, the connection to the database is defined in an elixir-specific object (a keyword-list) that specifies each part individually, with some parts of it being interpolated from the environment. In order to know the full definition of the connection, one has to have visibility over the code AND the environment variables. Changing the connection may require changing code and deploying a new release, or just changing the environment, or both (in a synchronized fashion!) depending on what needs changing.
Advantages:
- Consistent syntax that can reference in an uniform way several “systems”: PostgreSQL, Redis, Rabbit, S3, LDAP, External APIs.. Pretty much anything that can be identified as “a resource somewhere”
- Connection details fully defined by a single value in the environment, instead of being split across code/release and environment
- Environment variables friendly, which makes them ideal for containerized deployment systems
Disadvantages:
- Not all framework and libraries may support connection URIs, or they may offer partial support; for example no support for specifying connection settings like pool size or timeouts in the connection URI query params.
- No implicit “inheritance” for connection details with common defaults, e.g. port 5432 for connections to PostgreSQL clusters. I would actually consider this an advantage, as it minimizes the change for surprising behaviour.
- Environment variables that may be on the longish side, e.g.
STONEHENGE_DATABASE_URI=postgres://stonehenge_app:NfdsounHGgbefdsgsdds@db-production.helloprima.co.uk/stonehenge_db?pool_size=120&connect_timeout=100&keepalive=0 - Being one single value, all the parts of the connection details (host, port, database name) need to be “as secret” as the most secret part of it (i.e. authentication credentials); potentially limiting access and visibility.