├── dependency_injection ├── test │ ├── test_helper.exs │ ├── patterns_test.exs │ ├── bad_fetcher_test.exs │ └── good_fetcher_test.exs ├── lib │ ├── bad_fetcher.ex │ ├── good_fetcher.ex │ └── patterns.ex ├── README.md ├── .gitignore ├── mix.exs ├── config │ └── config.exs └── mix.lock ├── flow ├── chain_flow_funs.ex ├── direct_flow.ex └── chain_flow_procs.ex ├── style ├── error_return.ex ├── bad_looking_code.ex └── exceptions.ex ├── time_injection └── rotating_file_writer.ex └── README.md /dependency_injection/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /dependency_injection/test/patterns_test.exs: -------------------------------------------------------------------------------- 1 | defmodule PatternsTest do 2 | use ExUnit.Case 3 | 4 | end 5 | -------------------------------------------------------------------------------- /dependency_injection/test/bad_fetcher_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BadFetcherTest do 2 | use ExUnit.Case 3 | 4 | import Mock 5 | 6 | test "fetch, Stubr HTTPoison stub" do 7 | {:ok, pid} = BadFetcher.start_link([]) 8 | 9 | with_mock HTTPoison, [get!: &%{body: "url: #{&1}"}] do 10 | assert BadFetcher.fetch(pid, "http://ya.ru") == "url: http://ya.ru" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /flow/chain_flow_funs.ex: -------------------------------------------------------------------------------- 1 | defmodule EventReceiver do 2 | def handle(event) do 3 | Handler0.handle(event) 4 | end 5 | end 6 | 7 | defmodule Handler0 do 8 | def handle(event) do 9 | :timer.sleep(1) 10 | IO.inspect("Handler0, handled #{inspect event}") 11 | Handler1.handle(event) 12 | end 13 | end 14 | 15 | defmodule Handler1 do 16 | def handle(event) do 17 | :timer.sleep(1) 18 | IO.inspect("Handler1, handled #{inspect event}") 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /dependency_injection/lib/bad_fetcher.ex: -------------------------------------------------------------------------------- 1 | defmodule BadFetcher do 2 | use GenServer 3 | 4 | def start_link(opts) do 5 | GenServer.start_link(__MODULE__, [], opts) 6 | end 7 | 8 | def init([]) do 9 | {:ok, []} 10 | end 11 | 12 | def fetch(pid, url) do 13 | GenServer.call(pid, {:fetch, url}) 14 | end 15 | 16 | def handle_call({:fetch, url}, _from, state) do 17 | result = HTTPoison.get!(url).body 18 | {:reply, result, state} 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /dependency_injection/README.md: -------------------------------------------------------------------------------- 1 | # Patterns 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `patterns` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [{:patterns, "~> 0.1.0"}] 13 | end 14 | ``` 15 | 16 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 17 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 18 | be found at [https://hexdocs.pm/patterns](https://hexdocs.pm/patterns). 19 | 20 | -------------------------------------------------------------------------------- /dependency_injection/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /dependency_injection/lib/good_fetcher.ex: -------------------------------------------------------------------------------- 1 | defmodule GoodFetcher do 2 | use GenServer 3 | 4 | defstruct [:http] 5 | 6 | def start_link(args \\ []) do 7 | opts = Keyword.get(args, :opts, []) 8 | GenServer.start_link(__MODULE__, args, opts) 9 | end 10 | 11 | def init(args) do 12 | http = Keyword.get(args, :http, HTTPoison) 13 | {:ok, %GoodFetcher{http: http}} 14 | end 15 | 16 | def fetch(pid, url) do 17 | GenServer.call(pid, {:fetch, url}) 18 | end 19 | 20 | def handle_call({:fetch, url}, _from, state) do 21 | result = state.http.get!(url).body 22 | {:reply, result, state} 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /dependency_injection/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Patterns.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :patterns, 6 | version: "0.1.0", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | def application do 14 | [ 15 | mod: {Patterns, []}, 16 | extra_applications: [:logger] 17 | ] 18 | end 19 | 20 | defp deps do 21 | [ 22 | {:httpoison, "~> 0.10.0"}, 23 | {:doppler, "~> 0.1.0", only: :test}, 24 | {:stubr, "~> 1.5.0", only: :test}, 25 | {:mock, "~> 0.2.0", only: :test}, 26 | {:poolboy, "~> 1.5"} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /flow/direct_flow.ex: -------------------------------------------------------------------------------- 1 | defmodule EventReceiver do 2 | def handle(event) do 3 | event 4 | |> Handler0.handle() 5 | |> Handler1.handle() 6 | end 7 | 8 | def start do 9 | GenServer.start_link(Handler1, [], name: Handler1) 10 | end 11 | end 12 | 13 | defmodule Handler0 do 14 | def handle(event) do 15 | :timer.sleep(1) 16 | IO.inspect("Handler0, handled #{inspect event}") 17 | event 18 | end 19 | end 20 | 21 | defmodule Handler1 do 22 | use GenServer 23 | 24 | def handle(event) do 25 | GenServer.call(__MODULE__, {:handle, event}) 26 | end 27 | 28 | def handle_call({:handle, event}, _from, st) do 29 | :timer.sleep(1) 30 | IO.inspect("Handler1, handled #{inspect event}") 31 | {:reply, event, st} 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /dependency_injection/lib/patterns.ex: -------------------------------------------------------------------------------- 1 | defmodule Patterns do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | import Supervisor.Spec, warn: false 6 | 7 | children = [ 8 | worker(GoodFetcher, [[opts: [name: GoodFetcher]]]) 9 | ] 10 | 11 | 12 | poolboy_config = [ 13 | name: {:local, GoodFetcherPool}, 14 | worker_module: GoodFetcher, 15 | size: 30, 16 | max_overflow: 1 17 | ] 18 | 19 | poolboy_children = [ 20 | :poolboy.child_spec(GoodFetcherPool, poolboy_config, []) 21 | ] 22 | # :poolboy.transaction(GoodFetcherPool, fn(pid) -> GoodFetcher.fetch(pid, "https://ya.ru") end) 23 | 24 | opts = [strategy: :one_for_one, name: Patterns.Supervisor] 25 | Supervisor.start_link(children ++ poolboy_children, opts) 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /style/error_return.ex: -------------------------------------------------------------------------------- 1 | defmodule ErrorReturn do 2 | 3 | def sqrt_good(x) when is_float(x) and x >= 0 do 4 | {:ok, :math.sqrt(x)} 5 | end 6 | def sqrt_good(x) when is_float(x) do 7 | :error 8 | end 9 | 10 | def sqrt_bad(x) when is_float(x) and x >= 0 do 11 | :math.sqrt(x) 12 | end 13 | def sqrt_bad(x) when is_float(x) do 14 | :error 15 | end 16 | 17 | @doc """ 18 | Log of sqrt of the last number in a string 19 | 20 | ## Examples 21 | 22 | iex> log_sqrt_last("a b 10000") 23 | 2.0 24 | 25 | """ 26 | 27 | def log_sqrt_last(str) do 28 | with words <- String.split(str, ~r/\s+/), 29 | last_word <- List.last(words), 30 | {last_number, ""} <- Float.parse(last_word), 31 | {:ok, sqrt} <- sqrt_good(last_number), 32 | # sqrt when sqrt != :error <- sqrt_bad(last_number), # :(( 33 | log <- :math.log10(sqrt) 34 | do 35 | {:ok, log} 36 | end 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /flow/chain_flow_procs.ex: -------------------------------------------------------------------------------- 1 | defmodule EventReceiver do 2 | def handle(event) do 3 | Handler0.handle(event) 4 | end 5 | 6 | def start do 7 | for handler <- [Handler0, Handler1] do 8 | GenServer.start_link(handler, [], name: handler) 9 | end 10 | end 11 | end 12 | 13 | defmodule Handler0 do 14 | use GenServer 15 | 16 | def handle(event) do 17 | GenServer.cast(__MODULE__, {:handle, event}) 18 | end 19 | 20 | def handle_cast({:handle, event}, st) do 21 | :timer.sleep(1) 22 | IO.inspect("Handler0, handled #{inspect event}") 23 | Handler1.handle(event) 24 | {:noreply, st} 25 | end 26 | end 27 | 28 | defmodule Handler1 do 29 | use GenServer 30 | 31 | def handle(event) do 32 | GenServer.cast(__MODULE__, {:handle, event}) 33 | end 34 | 35 | def handle_cast({:handle, event}, st) do 36 | :timer.sleep(1) 37 | IO.inspect("Handler1, handled #{inspect event}") 38 | {:noreply, st} 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /dependency_injection/test/good_fetcher_test.exs: -------------------------------------------------------------------------------- 1 | defmodule GoodFetcherTest do 2 | use ExUnit.Case 3 | alias :doppler, as: Doppler 4 | 5 | defmodule HttpClient do 6 | def get!(url) do 7 | %{body: "url: #{url}"} 8 | end 9 | end 10 | 11 | test "fetch, manual http client double" do 12 | {:ok, pid} = GoodFetcher.start_link(http: HttpClient) 13 | assert GoodFetcher.fetch(pid, "http://ya.ru") == "url: http://ya.ru" 14 | end 15 | 16 | test "fetch, doppler http client double" do 17 | http = Doppler.start(nil) 18 | Doppler.def(http, :get!, fn(st, url) -> { %{body: "url: #{url}"}, st} end) 19 | 20 | {:ok, pid} = GoodFetcher.start_link(http: http) 21 | assert GoodFetcher.fetch(pid, "http://ya.ru") == "url: http://ya.ru" 22 | end 23 | 24 | test "fetch, Stubr http client double" do 25 | http = Stubr.stub!([get!: &%{body: "url: #{&1}"}]) 26 | 27 | {:ok, pid} = GoodFetcher.start_link(http: http) 28 | assert GoodFetcher.fetch(pid, "http://ya.ru") == "url: http://ya.ru" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /time_injection/rotating_file_writer.ex: -------------------------------------------------------------------------------- 1 | defmodule RotatingFileWriter do 2 | 3 | use GenServer 4 | 5 | @default_check_interval 1000 6 | 7 | def init([... ,check_interval , ...]) do 8 | ... 9 | timer_ref = :erlang.start_timer(check_interval, self, :tick) 10 | st = %{ 11 | ... 12 | timer_ref: timer_ref 13 | ... 14 | } 15 | {:ok, st} 16 | end 17 | 18 | def check_file_name_actuality(pid, time \\ now) do 19 | GenServer.cast(pid, {:check_file_name_actuality, time}) 20 | end 21 | 22 | defp now do 23 | Timex.now 24 | end 25 | 26 | def handle_cast({:check_file_name_actuality, time}, st) do 27 | new_st = maybe_reopen_file(time, st) 28 | {:noreply, new_st} 29 | end 30 | 31 | def handle_info({:timeout, _timer_ref, :tick}, st) do 32 | new_timer_ref = :erlang.start_timer(st.check_interval, self, :check_file_name_actuality) 33 | :erlang.cancel_timer(st.timer_ref) 34 | check_file_name_actuality(self) 35 | {:noreply, %{st | timer_ref: new_timer_ref}} 36 | end 37 | 38 | ... 39 | 40 | end 41 | -------------------------------------------------------------------------------- /style/bad_looking_code.ex: -------------------------------------------------------------------------------- 1 | defmodule BadLookingCode do 2 | use GenServer 3 | 4 | def handle_call({:event, url, bucket}, _from, state) do 5 | state = if state.current_bucket do 6 | delete_current_bucket(state) 7 | new_bucket = %{prefix: "events", data: []} 8 | %{state | current_bucket: new_bucket} 9 | else 10 | state 11 | end 12 | 13 | state = set_new_listeners(state) 14 | 15 | if good_url?(state, url) do 16 | hooks = before_hooks(state, url) 17 | 18 | good_hooks = hooks 19 | |> Enum.map(&("act_#{&1}")) 20 | |> Enum.filter(&(!Regex.match?(&1, ~r/\d{4}-\d{2}-\d{2}/))) 21 | |> eval_hook(true) 22 | 23 | Logger.info("Good before hooks: #{inspect good_hooks}") 24 | 25 | hooks = after_hooks(state, url) 26 | 27 | good_hooks = hooks 28 | |> Enum.map(&("act_#{&1}_a")) 29 | |> Enum.filter(&(!Regex.match?(&1, ~r/\d{4}-\d{2}-\d{2}/))) 30 | |> eval_hook(true) 31 | 32 | Logger.info("Good after hooks: #{inspect good_hooks}") 33 | {:reply, {good_hooks, url}, state} 34 | else 35 | {:reply, bucket, state} 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /dependency_injection/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :patterns, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:patterns, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /dependency_injection/mix.lock: -------------------------------------------------------------------------------- 1 | %{"agent": {:hex, :agent, "0.1.0", "463819be23aac571c45579d5ef7dd2155803ed885cd95e66f9550216847b96e9", [:rebar3], []}, 2 | "certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []}, 3 | "doppler": {:hex, :doppler, "0.1.0", "398461a44b5d459df7b8c7c2fd72fc7e00f5a15cbacf7575593a61ef28f92d88", [:rebar3], [{:agent, "~> 0.1.0", [hex: :agent, optional: false]}]}, 4 | "hackney": {:hex, :hackney, "1.6.5", "8c025ee397ac94a184b0743c73b33b96465e85f90a02e210e86df6cbafaa5065", [:rebar3], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]}, 5 | "httpoison": {:hex, :httpoison, "0.10.0", "4727b3a5e57e9a4ff168a3c2883e20f1208103a41bccc4754f15a9366f49b676", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, optional: false]}]}, 6 | "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []}, 7 | "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:make, :rebar], []}, 8 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, 9 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, 10 | "mock": {:hex, :mock, "0.2.1", "bfdba786903e77f9c18772dee472d020ceb8ef000783e737725a4c8f54ad28ec", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, optional: false]}]}, 11 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, 12 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []}, 13 | "stubr": {:hex, :stubr, "1.5.0", "68752582482a4af06b88bd718d652026ac4216508e7097c4aae7b924c4b9bc4d", [:mix], []}} 14 | -------------------------------------------------------------------------------- /style/exceptions.ex: -------------------------------------------------------------------------------- 1 | defmodule User do 2 | defstruct [:login, :password, :name] 3 | @type t :: %User{} 4 | end 5 | 6 | defmodule ValidateUserGood do 7 | 8 | @type validate_error :: :invalid_login | :invalid_password | :invalid_name 9 | 10 | @spec validate(User.t) :: :ok | {:error, validate_error} 11 | def validate(%User{} = user) do 12 | with :ok <- validate_login(user), 13 | :ok <- validate_password(user), 14 | :ok <- validate_name(user) 15 | do 16 | :ok 17 | else 18 | error -> {:error, error} 19 | end 20 | end 21 | 22 | @login_re ~r/\A\w{5,20}\z/ 23 | @min_password_length 8 24 | @name_re ~r/\A[\w ]+\z/ 25 | 26 | defp validate_login(%User{login: login}) do 27 | if Regex.match?(@login_re, login), do: :ok, else: :invalid_login 28 | end 29 | 30 | defp validate_password(%User{password: password}) do 31 | if String.length(password) >= @min_password_length, do: :ok, else: :invalid_password 32 | end 33 | 34 | defp validate_name(%User{name: name}) do 35 | if Regex.match?(@name_re, name), do: :ok, else: :invalid_name 36 | end 37 | end 38 | 39 | defmodule ValidateUserBad do 40 | 41 | defmodule LoginError do 42 | defexception message: "invalig login" 43 | end 44 | 45 | defmodule PasswordError do 46 | defexception message: "invalig password" 47 | end 48 | 49 | defmodule NameError do 50 | defexception message: "invalig name" 51 | end 52 | 53 | @spec validate!(User.t) :: :ok | no_return # WOW, such useful, very functional 54 | def validate!(%User{} = user) do 55 | validate_login!(user) 56 | validate_password!(user) 57 | validate_name!(user) 58 | :ok 59 | end 60 | 61 | @login_re ~r/\A\w{5,20}\z/ 62 | @min_password_length 8 63 | @name_re ~r/\A[\w ]+\z/ 64 | 65 | defp validate_login!(%User{login: login}) do 66 | unless Regex.match?(@login_re, login), do: raise LoginError 67 | end 68 | 69 | defp validate_password!(%User{password: password}) do 70 | unless String.length(password) >= @min_password_length, do: raise PasswordError 71 | end 72 | 73 | defp validate_name!(%User{name: name}) do 74 | unless Regex.match?(@name_re, name), do: raise NameError 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Стиль 2 | 3 | ### Говнокод 4 | 5 | Условный пример: [bad_looking_code.ex](style/bad_looking_code.ex). 6 | Код, выглядящий так даже издалека, всегда плох. В нем: 7 | - смешаны разные уровни абстракции; 8 | - копипаста; 9 | - перегрузка обязанностей; 10 | - непрозрачное переопредееление переменных; 11 | - ... 12 | 13 | В таком коде всегда будут плодиться ошибки. 14 | 15 | ### Возврат Ok/Error 16 | 17 | Возврат `{:ok, ok_result}` и `{:error, error}` -- это стандарт. 18 | Положительный и отрицательный результаты должны легко матчиться для возможности 19 | просто использовать эмуляцию монады `Maybe` или `Either` через `with`. 20 | 21 | Примеры: [error_return.ex](style/error_return.ex). 22 | 23 | ### Exceptions vs errors 24 | 25 | Не стоит использовать Exceptions для контроля бизнес логики. Это усложняет тип функций, 26 | лишает возможности его описать. 27 | 28 | Exceptions -- это возможность нормально сообщить, почему код все бросает и падает. 29 | 30 | [exceptions.ex](style/exceptions.ex) 31 | 32 | 33 | ## Dependency Injection 34 | 35 | Два фетчера: 36 | - [bad_fetcher.ex](dependency_injection/lib/bad_fetcher.ex) 37 | - [good_fetcher.ex](dependency_injection/lib/good_fetcher.ex) 38 | 39 | В первом изпользуемый HTTP клиент захардкожен, во втором передается. 40 | 41 | При тестировании первого приходится тестировать внутреннюю реализацию модуля, 42 | а не его публичный интерфейс, что сильно снижает ценность теста, т.к. он ломается 43 | при изменении внутренней реализации, а должен, наоборот, помогать рефакторить ее, 44 | оставаясь зеленым при корректном рефакторинге и сохранении интерфейса. 45 | 46 | Тест второго модуля специфицирует реальный контракт с модулем. 47 | 48 | ## Dependency Injection времени 49 | 50 | При работе с таймаутами лучше не использовать таймеры. Это сложный внешний state, за 51 | которым приходится следить и который трудно тестировать. 52 | 53 | Для некритичных к идеальной точности таймеров удобно использовать `tick`, посылку 54 | текущего времени самому себе с указанным интервалом: 55 | [rotating_file_writer.ex](time_injection/rotating_file_writer.ex). 56 | 57 | Тогда state таймеров превращается в обыкновенный конечный автомат, 58 | который легко тестировать извне. 59 | 60 | Пример: [smpp_timers.ex](https://github.com/savonarola/smppex/blob/master/lib/smppex/smpp_timers.ex) -- 61 | сложная композиция из четырех таймеров SMPP. В `oserl` реализована с багами через 62 | установку и отмену таймеров. 63 | 64 | ## Простейшие паттерны/антипаттерны параллельности 65 | 66 | - Антипаттерн: синхронные операции в "общем" `GenServer`: [good_fetcher.ex](dependency_injection/lib/good_fetcher.ex). При глобальном использовании 67 | представляет из себя бутылочное горлышко. 68 | - Антипаттерн: возможная неявная синхронизация во внешней либе (`HTTPoison`) даже при наличии пула. 69 | Бутылочное горлышко может быть спрятано и во внешней либе. 70 | 71 | ## Организация последовательной обработки 72 | 73 | - [chain_flow_procs.ex](flow/chain_flow_procs.ex) -- Плохо. Event кидается 74 | от процесса к процессу. Неконтролируемая нагрузка, низкая/неконтролируемая параллелность, 75 | неконтролируемая емкость системы. 76 | - [chain_flow_funs.ex](flow/chain_flow_funs.ex) -- Не так плохо, но тоже плохо. 77 | Event кидается из функции в функцию, сложно отслеживать и расширять поток. 78 | - [direct_flow.ex](flow/direct_flow.ex) -- Хорошо. Свой поток выполнения для каждого 79 | Event'а, неважно, что из себя представляют обработчики, можно легко измерить статистику 80 | по каждой стадии обработки, легко управлять стадиями. 81 | --------------------------------------------------------------------------------