发布于 2026-01-05 1 阅读
0

如何使用 Elixir 和 ScyllaDB 编写 CRUD CLI?DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

如何使用 Elixir 和 ScyllaDB 编写 CRUD CLI

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

如果您了解高流量通信应用,以及那些需要低延迟和良好容错能力的应用,那么您很可能已经听说过 Elixir(一种编程语言)和 ScyllaDB(一种面向低延迟的 NoSQL 数据库)。两者的目标非常相似:都是为了那些通常对稳定性要求更高的应用而设计的。

ScyllaDB 因其基于 Apache Cassandra 而享誉全球,被公认为速度极快的数据库,并在低延迟方面进行了多项改进。此外,ScyllaDB 完全免费、开源,并以 GNU AGPL 许可证发布。

另一方面,Elixir 是一种以很好地处理并发和容错等概念而闻名的编程语言,这要归功于 Erlang 生态系统,在这种情况下,Elixir 使用了名为 BEAM 的虚拟机,该虚拟机专门为处理高容量消息传递应用程序而设计。

本文旨在介绍如何使用这两项技术创建您的第一个应用程序,从而为您的未来开发打开大门,并带来无限可能。

目录

项目启动

事先我们需要安装 Elixir 和 ScyllaDB。为了简化文章,我不会详细介绍安装过程。那么,让我们先从安装 Elixir 开始。

安装 Elixir

通常情况下,安装 Elixir 主要有两种方法:直接从包管理器安装,或者使用编程语言版本管理器。我将使用asdfpip 来管理我的 Elixir 版本。如果您想使用包管理器安装 Elixir,请点击此处了解更多信息。

asdf您可以点击此处查看安装方法。我个人偏好使用“Bash & Git”或“Zsh & Git”进行安装。安装完成后,我们将配置项目,以便获取最新版本的 Erlang 和 Elixir。Erlang 安装步骤如下:

$ asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git

$ asdf install erlang latest

$ asdf global erlang latest
Enter fullscreen mode Exit fullscreen mode

安装过程中遇到问题了吗?请访问官方指南

安装 Elixir:

$ asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git

$ asdf install elixir latest

$ asdf global elixir latest
Enter fullscreen mode Exit fullscreen mode

安装过程中遇到问题了吗?请访问官方指南

好了,现在两者都已安装完毕,我们可以通过在终端模拟器中输入命令来测试一切是否正常elixir -v,我们会得到类似如下的响应:

Erlang/OTP 25 [erts-13.2.2.2] [source] [64-bit]

Elixir 1.13.4 (compiled with Erlang/OTP 23)
Enter fullscreen mode Exit fullscreen mode

这些是我安装的 Erlang 和 Elixir 版本。如果您想在您的机器上安装与我相同的版本,只需修改命令asdf install,将 `<version>` 替换latest为版本号,然后将其设置为 `<version>`(asdf global elixir 1.13.4在本例中为 `<version>`)。

安装 ScyllaDB

使用 ScyllaDB 有多种方法,您可以将其与ScyllaDB Cloud一起使用,也可以将其与安装在您的机器上的ScyllaDB 一起使用,或者像我将要使用的那样:使用 Docker 容器。

如果您尚未安装 Docker,我建议您访问[安装]指南(https://docs.docker.com/engine/install/)。如果您想使用 ScyllaDB Cloud 或已安装在您机器上的 ScyllaDB,也没有问题,只需在初始化和配置项目时小心放置节点链接即可,稍后我会详细解释。

好的,继续……要在 Docker 中运行包含 ScyllaDB 的容器,我们将使用以下命令:

$ docker run --name some-scylla -p 9042:9042 -d scylladb/scylla
Enter fullscreen mode Exit fullscreen mode

-p选项表示我们希望将容器的 9042 端口绑定到我们机器的 9042 端口,从而允许现在直接在我们的机器上访问我们的容器localhost:9042

为了测试连接,执行命令后,请等待几秒钟,待容器内的所有程序正确启动后,再输入:

$ docker exec -it some-scylla cqlsh
Enter fullscreen mode Exit fullscreen mode

你会看到类似这样的回复:

Connected to at 172.17.0.2:9042.
[cqlsh 5.0.1 | Cassandra 3.0.8 | CQL spec 3.3.1 | Native protocol v4]
Use HELP for help.
cqlsh> 
Enter fullscreen mode Exit fullscreen mode

这是我们的控制台,我们可以在这里执行命令与 ScyllaDB 进行交互。默认情况下,使用的语言是CQL(Cassandra 查询语言),它与您可能已经接触过的标准数据库 SQL 非常相似。

好,我们运行一个简单的命令来描述keyspaces容器中的所有内容。keyspaces可以用一个简单的类比来定义:键空间基本上与使用关系数据库(例如 MySQL 或 PostgreSQL)时的数据库相同(定义略有不同,但我不会深入探讨)。

描述一下你的keyspaces跑步:

cqlsh> DESCRIBE KEYSPACES;
Enter fullscreen mode Exit fullscreen mode

您应该会看到类似这样的回复:

system_schema system_traces system_distributed
Enter fullscreen mode Exit fullscreen mode

我们还没有创建任何键空间keyspace,对吧?好,让我们来创建键空间,在本例中,它是为 `<keyspace>` 创建的media_player,使用以下命令:

cqlsh> CREATE KEYSPACE media_player
     WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'}
     AND durable_writes = true;
Enter fullscreen mode Exit fullscreen mode

接下来我们来创建表格:

cqlsh> CREATE TABLE media_player.songs (
     id uuid,
     titletext,
     album text,
     artist text,
     created_at timestamp,
     PRIMARY KEY (id, created_at)
);
Enter fullscreen mode Exit fullscreen mode

启动我们的项目

要启动一个新项目,请运行以下命令:

$ mix new media_player
Enter fullscreen mode Exit fullscreen mode

Mix不仅仅是 Elixir 的依赖管理器,它还能帮助我们运行和管理整个项目。默认情况下,Mix 已随 Elixir 一起安装。

项目将按以下结构创建:

.
├── README.md
├── lib
│   └── media_player.ex
├── mix.exs
Enter fullscreen mode Exit fullscreen mode

好了,现在我们的项目已经初始化完毕,可以开始玩了,打开你最喜欢的代码编辑器,开始吧。

配置项目

此时,我们将配置 Elixir 项目,安装并使用构建 CLI 所需的所有工具,并定义初始设置。

定义依赖关系

打开代码编辑器后,你会发现有一个名为 `.code` 的文件mix.exs。该文件负责定义我们项目的几个属性,包括开发过程中将使用的依赖项。

向下滚动页面,你会看到一个以defp deps do“...”开头的区域。正是在这个区域,我们将修改并插入以下依赖项:

# Run "mix help deps" to learn about dependencies.
defp deps
  [
    {:dotenv, "~> 3.0"},
    {:decimal, "~> 1.0"},
    {:xandra, "~> 0.14"},
    {:elixir_uuid, "~> 1.2"}
  ]
end
Enter fullscreen mode Exit fullscreen mode
  • Dotenv:dotenv 的 Elixir 版本。
  • 十进制:任意精度的十进制运算。
  • Xandra:适用于 Elixir 的快速、简单且强大的 Cassandra/ScyllaDB 驱动程序。
  • Elixir UUID:Elixir 的 UUID 生成器和实用工具。参见 RFC 4122。

好了,现在我们已经定义了依赖项,可以在终端模拟器中运行它了:

$ mix deps.get
Enter fullscreen mode Exit fullscreen mode

上面的命令将安装我们文件中定义的所有依赖项mix.exs

太好了!现在我们可以在项目中配置环境变量了.env

配置 .env

好,现在让我们.env在项目根目录(没错,与我们的 .py 文件位于同一层级mix.exs)创建一个名为 .py 的文件。它将负责定义项目的初始配置,包括用于连接集群的环境变量。

创建文件并在代码编辑器中打开它时,我们将定义:

SCYLLADB_USERNAME=
SCYLLADB_PASSWORD=
SCYLLADB_NODE=
SCYLLADB_KEYSPACE=
SCYLLADB_TABLE=
Enter fullscreen mode Exit fullscreen mode
  • SCYLLADB_USERNAME: 已配置用于连接到 ScyllaDB 的用户名。
  • SCYLLADB_PASSWORD:为用户配置的密码。
  • SCYLLADB_NODE:连接到我们节点的完整 URL,您可以只输入一个 URL(例如,localhost:9042例如),或者定义用逗号分隔的完整节点(例如scylla-node1.com,scylla- node-2.com,scylla-node-3.com)。
  • SCYLLADB_KEYSPACE为我们的应用程序生成的键空间。
  • SCYLLADB_TABLE:将用于相应键空间的表。

这样,我们的 .env 文件应该如下所示:

SCYLLADB_USERNAME=scylla
SCYLLADB_PASSWORD=scylla
SCYLLADB_NODE=localhost:9042
SCYLLADB_KEYSPACE=media_player
SCYLLADB_TABLE=songs
Enter fullscreen mode Exit fullscreen mode

太好了!既然连接文件已经准备就绪,我们就可以开始构建项目结构了,好吗?

定义我们的连接模块

将连接模块分离到项目的独立区域中,既优雅又实用,便于维护,也使代码结构更加清晰。因此,我们创建两个目录,路径为 `<path>` lib/media_player/config。该目录将负责存储两个主要的配置文件:一个是用于连接集群的文件,另一个是用于定义键空间和表的文件。

好,我们在这个lib/media_player/config目录下创建两个文件,分别命名为`.htm`connection.ex和`.htm` database.ex。这样,我们的目录结构就变成了:

.
├── README.md
├── lib
│   ├── media_player
│   │   └── config
│   │   ├── connection.ex
│   │   └── database.ex
│   └── media_player.ex
├── mix.exs
Enter fullscreen mode Exit fullscreen mode

是的,我省略了该deps目录,因为它包含依赖项,也就是说,我们不需要手动修改任何内容,不用担心。此外,省略该test目录的另一个原因是它将包含可以实现的测试,但是,现在我们先不来实现它。

好了,现在我们可以定义与集群的连接了,首先从这个database.ex文件开始。

defmodule MediaPlayer.Config.Database do
  import Dotenv

  load()

  def start_link do
    options = [
      username: System.get_env("SCYLLADB_USERNAME"),
      password: System.get_env("SCYLLADB_PASSWORD")
    ]

    {:ok, cluster} =
      Xandra.Cluster.start_link(
        sync_connect: :infinity,
        authentication: {Xandra.Authenticator.Password, options},
        nodes:
          System.get_env("SCYLLADB_NODE")
          |> String.split(",")
      )

    cluster
  end
end
Enter fullscreen mode Exit fullscreen mode

在这个文件中,我们:

  • 我们导入该Dotenv库来管理文件中定义的变量.env
  • load()我们使用库中的函数加载变量Dotenv
  • 我们创建了一个名为 `connection_link` 的函数start_link,该函数将负责启动与集群的连接;
  • 在函数中,我们定义usernamepassword从文件中接收这些值.env
  • Xandra我们使用名为 `<function_name>`的函数初始化集群Xandra.Cluster.start_link,该函数负责启动与集群的连接。
    • 在这个函数中,我们定义了它的sync_connect:infinity!这意味着它将尝试建立连接,但预期响应时间是无限的(也就是说,模块将等待直到所有节点完全连接成功)。要了解更多信息,请点击此处
    • 我们定义我们将执行身份验证并将通行证options(之前已定义)作为参数;
    • 我们通过从文件中加载节点.env,并使用找到的逗号进行除法运算,将结果分发到一个列表中(nodes需要一个 URL 列表来建立连接,因此需要进行拆分以创建此列表)。如果您使用 ScyllaDB Cloud,这将使一切完美运行;
  • 我们返回连接已准备就绪的集群。

完美!我们的连接文件已准备就绪。现在让我们配置一个简单的区域,它只负责返回键空间和表,名为connection.ex

defmodule MediaPlayer.Config.Connection do
  import Dotenv

  load()

  def keyspace() do
    System.get_env("SCYLLADB_KEYSPACE")
  end

  def table() do
    System.get_env("SCYLLADB_TABLE")
  end
end
Enter fullscreen mode Exit fullscreen mode

该模块的唯一功能基本上就是提供两个函数来返回我们将要使用的键空间和表,而无需总是使用.env库!

使用数据库执行操作

好的,现在还有一点很重要:因为我们的项目会用到命令,所以创建一个专门的模块来处理这些命令会很有意义,对吧?太好了!不过在此之前,您觉得创建一个模块来执行数据库查询怎么样?这样我们就可以集中管理查询的执行位置了。

好了,现在是时候创建一个lib/media_player名为 . 的文件了actions.ex。所以我们的目录结构将如下所示:

.
├── README.md
├── lib
│   ├── media_player
│   │   ├── actions.ex
│   │   └── config
│   │   ├── connection.ex
│   │   └── database.ex
│   └── media_player.ex
├── mix.exs
Enter fullscreen mode Exit fullscreen mode

太棒了!有了创建的文件,我们现在可以创建两个特定的函数,但为什么要创建两个呢?很简单:该Xandra.Cluster.execute函数有两种变体,第一种有两个参数(集群和要执行的查询),第二种有三个参数(集群、要执行的查询以及参数列表,该列表主要用于准备我们的查询)。

那么,我们开始吧,我们的模块应该看起来像这样:

defmodule MediaPlayer.Actions do
  def cluster, do: MediaPlayer.Config.Database.start_link()

  def run_query(query) do
    case Xandra.Cluster.execute(cluster(), query) do
      {:ok, result} ->
        result

      {:error, error} ->
        IO.inspect(error)
    end
  end

  def run_query(query, params) do
    prepared = Xandra.Cluster.prepare!(cluster(), query)

    case Xandra.Cluster.execute(cluster(), prepared, params) do
      {:ok, result} ->
        result

      {:error, error} ->
        IO.inspect(error)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

在这个文件中,我们:

  • 我们定义一个本地cluster函数,该函数除了初始化并返回与集群的连接链接之外,什么也不做;
  • 我们定义一个run_query/1只接受一个参数的函数(毕竟,它只是查询,集群本身就是一个本地函数,我们知道我们总是会对其执行操作);
    • 我们尝试使用该函数执行查询Xandra.Cluster.execute
    • 如果返回值为真:ok,则表示一切顺利,我们将返回完整的结果(查询的完整映射);
    • 如果返回值为真,:error则表示执行查询时发生错误,那么我们将检查具体错误;

{:ok, result}一个重要的细节是关于使用 ` and`进行的赋值{:error, error},因为在 Elixir 中,所有东西都有一个返回值,总是以一个原子开头的映射,该原子验证给定的返回类型(我建议了解更多关于原子的信息),所以我们使用映射绑定值!

  • 我们定义一个函数run_query/2,它接受两个参数(只有查询语句和要执行的参数):
    • 我们尝试使用以下函数执行查询Xandra.Cluster.execute
    • 如果返回值为真:ok,则表示一切顺利,我们将返回完整的结果(查询的完整映射);
    • 如果返回值为真,:error则表示执行查询时发生错误,那么我们将检查具体错误;

如果您想了解更多关于该Xandra.Cluster.execute功能如何运作的信息,请点击这里

你可能没注意到一个细节:这两个函数名称相同,但它们的参数数量却不同!这正是 Elixir 的神奇之处。函数定义时会指定它需要接收的参数数量,所以我会在函数名后跟一个斜杠“/”,再后面加上参数数量。如果你想了解更多,可以点击这里更好地理解 Elixir 的模式匹配机制。

命令

好了,现在到了期待已久的时刻:添加负责在应用程序中执行命令的函数!为此,我们将创建一个lib/media_player名为 . 的文件commands.ex。这样,我们的目录结构将如下所示:

.
├── README.md
├── lib
│   ├── media_player
│   │   ├── actions.ex
│   │   ├── commands.ex
│   │   └── config
│   │   ├── connection.ex
│   │   └── database.ex
│   └── media_player.ex
├── mix.exs
Enter fullscreen mode Exit fullscreen mode

让我们先从构建整个模块的基础开始:

defmodule MediaPlayer.Commands do
  alias MediaPlayer.Actions, as: Actions
  alias MediaPlayer.Config.Connection, as: Connection

  defp keyspace, do: Connection.keyspace()
  defp table, do: Connection.table()
end
Enter fullscreen mode Exit fullscreen mode

基本上,以上我们已经定义了我们将拥有:

  • Actions指代该模块的别名MediaPlayer.Actions
  • Connection指代该模块的别名MediaPlayer.Config.Connection
  • keyspace返回我们将要使用的键空间值的私有函数;
  • table返回我们将要使用的表值的私有函数;

好了,现在我们可以开始执行这些命令了,好吗?

添加

这条命令将用于向我们的数据库添加歌曲。因此,我们将它拆分为两个主要函数,分别是 `addsingles`add和 `addsingles` add_from,这两个函数接收四个参数进行执行。`addsingles`add函数仅用于收集要插入的数据:

defmodule MediaPlayer.Commands do
  alias MediaPlayer.Actions, as: Actions
  alias MediaPlayer.Config.Connection, as: Connection

  defp keyspace, do: Connection.keyspace()
  defp table, do: Connection.table()

  def add_from(title, album, artist, created) do
    query =
      "INSERT INTO #{keyspace()}.#{table()} (id, title, album, artist, created_at) VALUES (?, ?, ?, ?, ?);"

    {:ok, created, _} = DateTime.from_iso8601(created <> "T00:00:00Z")

    Actions.run_query(query, [UUID.uuid4(), title, album, artist, created])

    IO.puts("Song added!")
  end

  def add() do
    title = IO.gets("Enter the title of the song: ") |> String.trim()
    album = IO.gets("Enter the album of the song: ") |> String.trim()
    artist = IO.gets("Enter the artist of the song: ") |> String.trim()

    created =
      IO.gets("Enter the date the song was created (YYYY-MM-DD): ")
      |> String.trim()

    add_from(title, album, artist, created)
  end
end
Enter fullscreen mode Exit fullscreen mode

这两个函数都非常具体,所以我们分别举个例子来说明:

  • add函数将:
    • 收集歌曲名称;
    • 收藏这张音乐专辑;
    • 找出歌曲演唱者;
    • 收集歌曲的创作日期;
    • 调用该add_from函数,并将收集到的值作为参数传递;
  • add_from/4函数将:
    • 接收四个参数;
    • 创建query将要执行的脚本;
    • 转换日期格式以确保与 Xandra 绑定兼容;
    • 调用该函数执行查询,并传递两个参数:查询内容和列表格式的附加选项(如前所述)。
    • 歌曲插入时,只需在屏幕上显示该消息即可!

另一点:Elixir 中的“|>”管道符与 Unix shell 中的“|”管道符类似,用于将一个函数的返回值作为下一个函数的第一个参数传递。点击此处阅读更多关于管道符的信息。

好了,现在我们有了负责添加已创建歌曲的函数!下次我们继续吧?

列表

现在我们来创建一个函数,负责列出所有已添加的歌曲。因此,我们将得到以下结果:

  def list
    query = "SELECT id, title, album, artist, created_at FROM #{keyspace()}.#{table()};"

    Actions.run_query(query)
    |> Enum.each(fn %{
                      "id" => id,
                      "title" => title,
                      "album" => album,
                      "artist" => artist,
                      "created_at" => created_at
                    } ->
      IO.puts(
        "ID: #{id} | Title: #{title} | Album: #{album} | Artist: #{artist} | Created At: #{created_at}"
      )
    end)
  end
Enter fullscreen mode Exit fullscreen mode

list函数不接收任何参数,它只会将添加到屏幕上的歌曲打印出来,如下所示:

  • id, title, album, artist, created_at我们通过选择来定义查询,keyspacetable记住我们已经有两个函数返回这些值!
  • 我们尝试执行一个简单的查询(除了 之外没有其他选项query),并将其返回值传递给一个Enum.each(类似于foreach其他编程语言中的 );
    • 在每个函数中,我们传递一个匿名函数来处理返回值,在本例中,该函数期望接收一个包含所选相应字段的映射;
    • 我们打印特定数值;
  • 最终,这将是我们的归宿;

一个重要的细节:在这种情况下,run_query如果我们插入了多行数据,它会返回多个值,对吗?实际上,run_query它会返回一个包含所有映射及其对应值的完整列表,因此我们用它Enum.each来处理列表中的每个索引。

删除

这个delete命令稍微复杂一些,因为我们需要用户输入要删除的索引,并将其与返回的索引列表进行比对!因此,实现的函数会生成类似这样的结果:

  def delete() do
    query = "SELECT id, title, album, artist, created_at FROM #{keyspace()}.#{table()};"

    songs =
      Actions.run_query(query)
      |> Enum.with_index(fn %{
                              "id" => id,
                              "title" => title,
                              "album" => album,
                              "artist" => artist,
                              "created_at" => created_at
                            },
                            index ->
        IO.puts(
          "Index: #{index + 1} | Title: #{title} | Album: #{album} | Artist: #{artist} | Created At: #{created_at}"
        )

        %{id: id, title: title, album: album, artist: artist, created_at: created_at}
      end)

    {input, _} = IO.gets("Enter the index of the song you want to delete: ") |> Integer.parse()

    case Enum.at(songs, input - 1) do
      %{} = song ->
        query = "DELETE FROM #{keyspace()}.#{table()} WHERE id = ? AND created_at = ?;"

        Actions.run_query(query, [song.id, song.created_at])

        IO.puts("Song deleted!")

      nil ->
        IO.puts("Invalid index.")
    end
  end
Enter fullscreen mode Exit fullscreen mode

基本上,这个函数包含以下内容:

  • 返回所有已添加歌曲的定义query
  • 列出与之前实现的list功能类似的功能;
    • 我们尝试执行查询Actions.run_query并将其传递给Enum.with_index索引;
    • 这次id我们不展示歌曲的编号,而是展示一个手动索引(不是从 0 开始,而是从 1 开始,因此index + 1),用户可以在其中输入(输入一个整数比输入一个 UUID 更实用,不是吗?);
    • 我们打印这些值;
    • 我们已将完整列表添加到songs
  • 我们等待用户输入要删除的索引,然后解析该条目并将其保存到input;
  • 我们检查输入的索引是否存在songs
    • 如果您是:
    • 我们创建查询;
    • 我们尝试执行查询Actions.run_query,并将选项列表作为参数添加到查询中;
    • 我们打印出歌曲已被删除的消息;
    • 如果不:
    • 我们打印出索引无效的信息。

这样我们就完成了删除歌曲的功能!

压力

太棒了!所有需要操作用户输入的命令都完成了!现在我们来创建一个额外的命令,用于对数据库进行压力测试。我们将定义两个函数:一个是stress初始化命令的函数,另一个是私有函数,generate_stress_query它接收一个名为 `<index>` 的参数some_id,该参数指定要插入的索引。

首先,我们必须在模块开头添加以下部分:

defmodule MediaPlayer.Commands do
  use Task
  ...
Enter fullscreen mode Exit fullscreen mode

我们可以更便捷、更高效地执行异步调用。点击此处Task了解更多信息

因此,我们实现以下功能:

  defp generate_stress_query(some_id) do
    current_date = Date.to_string(Date.utc_today())

    "INSERT INTO #{keyspace()}.#{table()} (
      id, title, album, artist, created_at
    ) VALUES (
      #{UUID.uuid4()},
      'Test Song #{some_id}',
      'Test Artist #{some_id}',
      'Test Album #{some_id}',
      '#{current_date}'
    );"
  end

  def stress
    start = Time.utc_now()
    cluster = MediaPlayer.Config.Database.start_link()

    # Simple stress test
    1..100_000
    |> Task.async_stream(
      fn id ->
        IO.puts("[#{id}] Adding seed")
        Xandra.Cluster.execute(cluster, generate_stress_query(id))
      end,
      max_concurrency: 500
    )
    |> Enum.to_list()

    IO.puts("Time taken: #{Time.diff(Time.utc_now(), start, :second)} seconds")
  end
Enter fullscreen mode Exit fullscreen mode

基本上我们有:

  • stress功能:
    • Time.utc_now()用;标记函数的开始时间。
    • 手动初始化集群,以获得更好的性能和处理能力。
    • 我们发起一个从 1 到 100000 的异步呼叫:
    • 定义一个接收参数的匿名函数id
    • 我们打印出正在添加某个索引的信息;
    • Xandra.Cluster.execute我们尝试通过调用generate_stress_query负责使用所提供的参数生成完整查询的函数来执行查询id
    • 我们设置了max_concurrency500 作为异步调用次数的限制;
    • 我们将其格式化为列表;
    • 我们打印出完成整个测试所花费的时间,计算当前时间与开始时间之间的差值(以秒为单位);
  • generate_stress_query功能:
    • 接收一个id参数来生成查询;
    • 设置要插入的当前日期;
    • 返回已格式化的完整查询;

好了,这样我们的压力测试功能就准备就绪了!现在我们必须实现用户可以输入的命令!

实现用户交互

让我们来修改主模块的MediaPlayer文件media_player.ex!首先,我们来为命令定义一个别名:

defmodule MediaPlayer
  alias MediaPlayer.Commands, as: Commands
end
Enter fullscreen mode Exit fullscreen mode

该别名将用于引用该MediaPlayer.Commands模块!

我们将实现的第一个函数是loop,它将负责指令的执行、接收用户输入并始终处于无限循环中等待输入,参见:

  def loop do
    IO.puts("-------------------------------------")
    IO.puts("Type any command: ")
    command = IO.gets("") |> String.trim()

    case command do
      "!add" ->
        Commands.add()
        loop()

      "!list" ->
        Commands.list()
        loop()

      "!delete" ->
        Commands.delete()
        loop()

      "!stress" ->
        Commands.stress()
        loop()

      "exit" ->
        IO.puts("Bye bye!")
        :OK

      _ ->
        IO.puts("Command not found!")
        loop()
    end
  end
Enter fullscreen mode Exit fullscreen mode

基本上,我们等待用户输入命令,然后输入case

  • !add调用该Commands.add()函数;
  • !list调用该Commands.list()函数;
  • !delete调用该Commands.delete()函数;
  • !stress调用该Commands.stress()函数;
  • exit打印一条告别消息并返回:ok,表示该函数不再进入递归循环,从而结束了我们的应用程序;
  • _打印“命令未找到”消息并进入递归循环,等待新的输入;

好了,现在我们的主函数已经准备好了!但是我们要如何执行它呢?很简单,我们创建一个start函数来负责启动我们的应用程序,此外还要创建一个run函数,作为启动函数调用的函数start,请参见:

  def start(_, _) do
    run()
    {:ok, self()}
  end

  def run do
    IO.puts("-------------------------------------")
    IO.puts("- ScyllaDB Cloud Elixir Media Player -")
    IO.puts("- Leave a star on the repo -")
    IO.puts("-------------------------------------")
    IO.puts("Here some possibilities")
    IO.puts(" !add - add new song")
    IO.puts(" !list - list all songs")
    IO.puts(" !delete - delete a specific song")
    IO.puts(" !stress - stress testing with mocked data")
    IO.puts("-------------------------------------")

    loop()
  end
Enter fullscreen mode Exit fullscreen mode

基本上我们有:

  • start功能:
    • 它接收两个参数,但我们不会使用它们,所以我们将它们定义为_
    • 调用该run函数;
    • 返回{:ok, self()}表示函数已成功执行,应用程序已关闭;
  • run功能:
    • 打印欢迎信息;
    • 打印包含命令选项的消息;
    • 调用该loop函数以启动无限循环;

我们的申请流程到此结束!现在我们要执行它吗?

运行我们的应用程序

好了,现在我们的应用程序已经准备就绪,让我们执行命令:

$ mix run
Enter fullscreen mode Exit fullscreen mode

这样我们就可以开始与应用程序互动了!

查看项目演示:

asciicast

结论

非常感谢您读到这里!本文旨在演示如何将 ScyllaDB 与 Elixir 结合使用,并以 Xandra 作为两者之间的连接桥梁进行简单演示。我强烈推荐您关注ScyllaDB University的内容,获取更多高质量的教育资源,同时也推荐您阅读DanielHe4rtCherry Ramatis撰写的关于 ScyllaDB 使用技巧的文章!他们两位都一直在贡献并发布精彩的内容。

我还建议了解一下“ScyllaDB Cloud 入门”项目,该项目演示了 ScyllaDB 的一般用法,但更深入地了解如何使用 ScyllaDB 自己的平台来管理云集群,是不是很酷?

在同一个仓库中,您也可以使用 Elixir 访问该项目,因此如果您想查看完整的源代码,请记住它就在上面提到的同一个链接中!

希望您喜欢这篇文章的内容,也希望它能帮助您更好地理解 ScyllaDB 与 Elixir 的结合使用。同时,我也希望您能对更多类似的内容感兴趣,或者想进一步学习 Elixir。非常感谢,下次再见!

文章来源:https://dev.to/guto/how-to-write-a-crud-cli-using-elixir-and-scylladb-4bek