Elixir — Firestore Database Integration

Sharing a way to integrate the GCP Firestore database with the elixir-based application.

Here, is the link to the GCP — library I am using for this demo https://hexdocs.pm/google_api_firestore/

Sharing Code repository for the mentioned problem here.

Pre-requisite

#Step 1 — Enable Firestore database for the GCP project

$ gcloud alpha firestore databases create --region=<region> --project <project_id>
Waiting for operation [apps/<project_id>/operations/0f8c69c9-89a6-4ffe-b70e-234b5779....] to complete...done.

#Step 2 — Enable Firestore API

#Step 3 — Provider Permission to Service Account

$ resource "google_project_iam_binding" "sa-firestore-permission-binding" {
project = <project_id>
role = "roles/datastore.owner"
members = [
"serviceAccount:<service_account_id>"
]
}

** Using IAC is recommended way to make account-level changes in cloud projects rather than making changes via GUI.

Elixir changes

# Step 1 — Add dependencies

[
{:goth, "~> 1.2"},
{:google_api_storage, "~> 0.19.0"},
{:google_api_firestore, "~> 0.22"}
]

# Step 2 — Enable google OAuth configuration

config :goth,  json: (System.get_env("SERVICE_ACCOUNT_BASE64_JSON_KEY") || "") |> Base.decode64!()

#Step 3 — Define Firestore module.

defmodule Demo.Firestore.DAO do@moduledoc false  alias GoogleApi.Firestore.V1.Api.Projects
alias GoogleApi.Firestore.V1.Connection
alias GoogleApi.Firestore.V1.Model.BeginTransactionResponse
alias GoogleApi.Firestore.V1.Model.GqlQuery
alias GoogleApi.Firestore.V1.Model.RunQueryRequest
alias Goth.Token
alias RN.GCP.FirestoreTransformer
require Logger
@datastore_auth_request_url "https://www.googleapis.com/auth/datastore"def begin_transaction(conn, database) do
Projects.firestore_projects_databases_documents_begin_transaction(
conn,
database
)
|> case do
{:ok, %BeginTransactionResponse{transaction: transaction}} -> {:ok, transaction}
error ->
Logger.error("error #{inspect(error)}")
{:error, :begin_transaction_failure}
end
end
def prepare_commit_request(document, transaction) do
request = %GoogleApi.Firestore.V1.Model.CommitRequest{
transaction: transaction,
writes: [
%GoogleApi.Firestore.V1.Model.Write{
update: document
}
]
}
{:ok, request}
end
def commit(conn, database, request) do
Projects.firestore_projects_databases_documents_commit(
conn,
database,
body: request
)
|> case do
{:ok, _} ->
{:ok, :delivery_details_persisted}
err ->
Logger.error("error #{inspect(err)}")
{:error, :delivery_details_persistance_failure}
end
end
def save(project,document) do
database = "projects/#{project}/databases/(default)"
with {:ok, token} <- Token.for_scope(@datastore_auth_request_url),
conn <- Connection.new(token.token),
{:ok, transaction} <- begin_transaction(conn, database),
{:ok, request} <- prepare_commit_request(document, transaction),
{:ok, :delivery_details_persisted} <- commit(conn, database, request) do
{:ok, :entity_saved}
else
{:error, res} ->
Logger.error("#{inspect(res)}")
{:error, :datastore_persist_entity_failure}
err ->
Logger.error("#{inspect(err)}")
{:error, :datastore_persist_entity_failure}
end
end
end

# Step 4 — Let’s Test

{:ok, now} = DateTime.now("Etc/UTC")d = %GoogleApi.Firestore.V1.Model.Document{
name: "projects/<project_id>/databases/(default)/documents/demo-elixir-firestore/123",
createTime: now,
updateTime: now,
fields: %{
"order_id" => %GoogleApi.Firestore.V1.Model.Value{
stringValue: "123"
}}}
assert {:ok,_} = Demo.Firestore.DAO.save("<project_id>",d)

Thank you for reading!

Father || Coder || Engineer || Learner || Reader || Writer