Elixir — Firestore Database Integration
2 min readNov 27, 2021
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
enddef prepare_commit_request(document, transaction) do
request = %GoogleApi.Firestore.V1.Model.CommitRequest{
transaction: transaction,
writes: [
%GoogleApi.Firestore.V1.Model.Write{
update: document
}
]
}
{:ok, request}
enddef 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
enddef 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!