Skip to content

Commit 1ecdd2d

Browse files
authored
"Using application configuration for libraries" added (Anti-patterns documentation) (#12944)
1 parent d101450 commit 1ecdd2d

File tree

1 file changed

+56
-1
lines changed

1 file changed

+56
-1
lines changed

lib/elixir/pages/anti-patterns/design-anti-patterns.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,59 @@ A common practice followed by the community is to make the non-raising version t
279279

280280
## Using application configuration for libraries
281281

282-
TODO
282+
#### Problem
283+
284+
The [*application environment*](https://hexdocs.pm/elixir/Application.html#module-the-application-environment) can be used to parameterize global values that can be used in an Elixir system. This mechanism can be very useful and therefore is not considered an anti-pattern by itself. However, library authors should avoid using the application environment to configure their library. The reason is exactly that the application environment is a **global** state, so there can only be a single value for each key in the environment for an application. This makes it impossible for multiple applications depending on the same library to configure the same aspect of the library in different ways.
285+
286+
#### Example
287+
288+
The `DashSplitter` module represents a library that configures the behavior of its functions through the global application environment. These configurations are concentrated in the *config/config.exs* file, shown below:
289+
290+
```elixir
291+
import Config
292+
293+
config :app_config,
294+
parts: 3
295+
296+
import_config "#{config_env()}.exs"
297+
```
298+
299+
One of the functions implemented by the `DashSplitter` library is `split/1`. This function aims to separate a string received via a parameter into a certain number of parts. The character used as a separator in `split/1` is always `"-"` and the number of parts the string is split into is defined globally by the application environment. This value is retrieved by the `split/1` function by calling `Application.fetch_env!/2`, as shown next:
300+
301+
```elixir
302+
defmodule DashSplitter do
303+
def split(string) when is_binary(string) do
304+
parts = Application.fetch_env!(:app_config, :parts) # <= retrieve parameterized value
305+
String.split(string, "-", parts: parts) # <= parts: 3
306+
end
307+
end
308+
```
309+
310+
Due to this parameterized value used by the `DashSplitter` library, all applications dependent on it can only use the `split/1` function with identical behavior about the number of parts generated by string separation. Currently, this value is equal to 3, as we can see in the use examples shown below:
311+
312+
```elixir
313+
iex> DashSplitter.split("Lucas-Francisco-Vegi")
314+
["Lucas", "Francisco", "Vegi"]
315+
iex> DashSplitter.split("Lucas-Francisco-da-Matta-Vegi")
316+
["Lucas", "Francisco", "da-Matta-Vegi"]
317+
```
318+
319+
#### Refactoring
320+
321+
To remove this anti-pattern and make the library more adaptable and flexible, this type of configuration must be performed via parameters in function calls. The code shown below performs the refactoring of the `split/1` function by accepting [keyword lists](`Keyword`) as a new optional parameter. With this new parameter, it is possible to modify the default behavior of the function at the time of its call, allowing multiple different ways of using `split/2` within the same application:
322+
323+
```elixir
324+
defmodule DashSplitter do
325+
def split(string, opts \\ []) when is_binary(string) and is_list(opts) do
326+
parts = Keyword.get(opts, :parts, 2) # <= default config of parts == 2
327+
String.split(string, "-", parts: parts)
328+
end
329+
end
330+
```
331+
332+
```elixir
333+
iex> DashSplitter.split("Lucas-Francisco-da-Matta-Vegi", [parts: 5])
334+
["Lucas", "Francisco", "da", "Matta", "Vegi"]
335+
iex> DashSplitter.split("Lucas-Francisco-da-Matta-Vegi") #<= default config is used!
336+
["Lucas", "Francisco-da-Matta-Vegi"]
337+
```

0 commit comments

Comments
 (0)