Search code examples
docker-composeelixirscyllagleamcassandra-driver-xandra

How to use xandra (elixir package) in gleam?


I wanted to try out ScyllaDB with Gleam programming language (am beginner) but struggling with establishing connection from gleam. I wasn't able to find any cassandra drivers in gleam so using @external attribute was my only go-to choice in this case.

I used docker to easily initialize 2 scylladb nodes :

services:
  gleam:
    build:
      context: .
      dockerfile: Dockerfile-dev
    # command: 
    container_name: server
    ports:
      - 9163:8080
    volumes:
      - ./wnw_backend/:/app/wnw_backend
    networks:
      - web

  scylladb-node1:
    image: scylladb/scylla:latest
    container_name: scylladb-node1
    ports:
      - '9042:9042'
      - '9160:9160'
      - '7004:7000'
      - '7005:7001'
      - '10000:10000'
    volumes:
      - type: volume
        source: target
        target: /var/lib/scylla
        volume:
          nocopy: true
    environment:
      - SCYLLA_ARGS=--developer-mode 1
    healthcheck:
      test: ["CMD-SHELL", "cqlsh -e 'SELECT now() FROM system.local' || exit 1"]
      start_period: 30s
      interval: 10s
      timeout: 10s
      retries: 10
    restart: always
    networks:
      - web

  scylladb-node2:
    image: scylladb/scylla:latest
    container_name: scylladb-node2
    depends_on:
      scylladb-node1:
        condition: service_healthy
    ports:
      - '9043:9042'
      - '9161:9160'
      - '7006:7000'
      - '7007:7001'
      - '10001:10000'
    volumes:
      - type: volume
        source: target
        target: /var/lib/scylla
        volume:
          nocopy: true
    environment:
      - SCYLLA_ARGS=--developer-mode 1
    command: --seeds=scylladb-node1
    healthcheck:
      test: ["CMD-SHELL", "cqlsh -e 'SELECT now() FROM system.local' || exit 1"]
      start_period: 30s
      interval: 10s
      timeout: 10s
      retries: 10
    restart: always
    networks:
      - web

volumes:
  target:

networks:
  web:
    driver: bridge
    # external: true

Code i currently written looks like this :

import app/router
import dot_env as dot
import dot_env/env
import gleam/dynamic.{type Dynamic}
import gleam/erlang/process
import gleam/int
import gleam/io
import mist
import wisp

pub fn main() {
  wisp.configure_logger()

  dot.new()
  |> dot.set_path("/app/.env")
  |> dot.set_debug(False)
  |> dot.load

  let assert Ok(secret_key) = env.get("SECRET_KEY_BASE")

  let assert Ok(conn) =
    start_link(["scylladb-node1:9042", "scylladb-node2:9042"])

  io.println("xandra" <> int.to_string(conn) <> ".")
  io.println(secret_key)

  let assert Ok(_) =
    router.handle_request(_)
    |> wisp.mist_handler(secret_key)
    |> mist.new()
    |> mist.port(8080)
    |> mist.start_http()

  process.sleep_forever()
}

@external(erlang, "Xandra", "start_link")
pub fn start_link(nodes: List(String)) -> Result(Int, Dynamic)

Compile log:

/app/wnw_backend # gleam run
  Compiling wnw_backend
   Compiled in 1.45s
    Running wnw_backend.main
exception error: undefined function 'Xandra':start_link/1
  in function  wnw_backend:main/0 (/app/wnw_backend/build/dev/erlang/wnw_backend/_gleam_artefacts/wnw_backend.erl, line 29)/app/wnw_backend #

Now I feel like i might've misunderstood the purpose of the @external attribute. If so, what am i missing?


Solution

  • Instead of directly using the start_link() from elixir, i created new elixir file that encodes strings to atom. And i believe this can be done in gleam as well. I was just too lazy so... anyway, here is my solution :

    src/xandra_wrapper.ex

    defmodule XandraWrapper do
      def start_link(config) do
        config_keyword = Enum.map(config, fn {k, v} -> {String.to_atom(k), v} end)
        Xandra.start_link(config_keyword)
      end
    end
    

    src/app/router/cluster.gleam

    import gleam/dynamic.{type Dynamic}
    
    @external(erlang, "Elixir.XandraWrapper", "start_link")
    pub fn start_link(config: List(#(String, Dynamic))) -> Result(Dynamic, Dynamic)
    

    src/main.gleam

    import app/router
    import gleam/io
    
    pub fn main() {
      ...
    
      let config = [#("nodes", dynamic.from(["scylla-node:9042"]))]
    
      let conn = cluster.start_link(config)
      io.debug(conn)
    
      ...
    }
    
    

    This should at least establish connection and return