lib/hare_mq.ex

defmodule HareMq do
  @moduledoc """
  To use HareMq in your Elixir project, follow these steps:

  Install the required dependencies by adding them to your `mix.exs` file:

  ```elixir
  defp deps do
  [
    {:hare_mq, "~> 1.3.0"}
  ]
  end
  ```

  ### Publisher

  The `MyApp.MessageProducer` module is responsible for publishing messages to a message queue using the `HareMq.Publisher` behavior. This module provides an interface for sending messages with specified routing and exchange settings. It also supports deduplication based on specific keys.

  The `MyApp.MessageProducer` module is configured with the following options:

  - **`routing_key`**: Specifies the routing key used to route messages to the appropriate queue. In this example, the routing key is set to `"routing_key"`.

  - **`exchange`**: Defines the exchange to which messages will be published. In this example, the exchange is set to `"exchange"`.

  - **`unique`**: This configuration option sets up deduplication rules:
  - **`period`**: Defines the time period for deduplication in ms. In this example, it is set to `:infinity`, meaning that messages are considered unique indefinitely based on the specified keys.
  - **`keys`**: A list of keys used to determine message uniqueness. If the message is a string, deduplication is managed by default behavior and you do not need to specify `keys`. If provided, deduplication is based on the specified keys. In the example, deduplication is based on the `:project_id` key.


  ```elixir
  defmodule MyApp.MessageProducer do
  use HareMq.Publisher,
    routing_key: "routing_key",
    exchange: "exchange"

  # Function to send a message to the message queue.
  def send_message(message) do
    # Publish the message using the HareMq.Publisher behavior.
    publish_message(message)
  end
  end
  ```

  ```elixir
  defmodule MyApp.MessageProducer do
  use HareMq.Publisher,
    routing_key: "routing_key",
    exchange: "exchange",
    unique: [
      period: :infinity,
      keys: [:project_id]
    ]

  # Function to send a message to the message queue.
  def send_message(message) do
    # Publish the message using the HareMq.Publisher behavior.
    publish_message(message)
  end
  end
  ```


  ### Consumer

  The `MyApp.MessageConsumer` module is designed to consume messages from a message queue using the `HareMq.Consumer` behavior. This module provides the functionality to receive and process messages with specific routing and exchange settings.

  The `MyApp.MessageConsumer` module is configured with the following options:

  - **`queue_name`**: Specifies the name of the queue from which messages will be consumed. In this example, the queue name is set to `"queue_name"`.

  - **`routing_key`**: Defines the routing key used to filter messages. In this example, the routing key is set to `"routing_key"`.

  - **`exchange`**: Defines the exchange from which messages will be consumed. In this example, the exchange is set to `"exchange"`.

  ```elixir
  defmodule MyApp.MessageConsumer do
  use HareMq.Consumer,
    queue_name: "queue_name",
    routing_key: "routing_key",
    exchange: "exchange"

  # Function to process a received message.
  def consume(message) do
    # Log the beginning of the message processing.
    IO.puts("Processing message: \#{inspect(message)}")
  end
  end
  ```

  ### Dynamic Consumer

  The `MyApp.MessageConsumer` module is designed to consume messages from a message queue using the `HareMq.DynamicConsumer` behavior. This module provides the functionality to receive and process messages with dynamic scaling based on the specified number of worker processes.

  The `MyApp.MessageConsumer` module is configured with the following options:

  - **`queue_name`**: Specifies the name of the queue from which messages will be consumed. In this example, the queue name is set to `"queue_name"`.

  - **`routing_key`**: Defines the routing key used to filter messages. In this example, the routing key is set to `"routing_key"`.

  - **`exchange`**: Defines the exchange from which messages will be consumed. In this example, the exchange is set to `"exchange"`.

  - **`consumer_count`**: Indicates the number of worker processes that should be used to handle incoming messages. In this example, `consumer_count` is set to `10`, which means that 10 worker processes will be run to consume and process messages concurrently.

  - **`auto_scaling`**: Allows configuration for dynamic scaling of consumers:
  - **`min_consumers`**: The minimum number of consumers to maintain.
  - **`max_consumers`**: The maximum number of consumers to maintain.
  - **`messages_per_consumer`**: The number of messages each consumer should handle before scaling adjustments are considered.
  - **`check_interval`**: The interval (in milliseconds) at which to check the queue length and adjust the number of consumers accordingly.

  ```elixir
  defmodule MyApp.MessageConsumer do
  use HareMq.DynamicConsumer,
    queue_name: "queue_name",
    routing_key: "routing_key",
    exchange: "exchange",
    consumer_count: 10

  # Function to process a received message.
  def consume(message) do
    # Log the beginning of the message processing.
    IO.puts("Processing message: \#{inspect(message)}")
  end
  end
  ```

  ### MessageConsumer with auto scaling
  ```elixir
  defmodule MyApp.MessageConsumer do
  use HareMq.DynamicConsumer,
    queue_name: "queue_name",
    routing_key: "routing_key",
    exchange: "exchange",
    consumer_count: 10,
    auto_scaling: [
      min_consumers: 1,
      max_consumers: 20,
      messages_per_consumer: 100,
      check_interval: 5_000
    ]

  # Function to process a received message.
  def consume(message) do
    # Log the beginning of the message processing.
    IO.puts("Processing message: \#{inspect(message)}")
  end
  end
  ```

  ### Usage in Application: MyApp.Application
  ```elixir
  defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      # Start the message consumer.
      MyApp.MessageConsumer,

      # Start the message producer.
      MyApp.MessageProducer,
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
  end
  ```
  ## Configuration
  ```elixir
  config :hare_mq,
  :amqp,
    host: "localhost",
    url: "amqp://guest:guest@myhost:12345",
    user: "guest",
    password: "guest"

  config :hare_mq, :configuration,
  delay_in_ms: 10_000,
  retry_limit: 15,
  message_ttl: 31_449_600
  ```

  ## Rate Us:
  If you enjoy using HareMq, please consider giving us a star on GitHub! Your feedback and support are highly appreciated.
  [GitHub](https://github.com/Dimakoua/hare_mq)
  """
  use Application
  require Logger

  def start(_type, _args) do
    children = [
      HareMq.Connection,
      HareMq.DedupCache
    ]

    opts = [strategy: :one_for_one, name: HareMq.Supervisor]
    Supervisor.start_link(children, opts)
  end
end