使用 Oracle Database 和 Oracle Cloud Autonomous Database 处理 Go 应用

这是关于 Go 和 Oracle Cloud Infrastructure (OCI) 的五部分系列的第四部分。本系列讨论如何在计算实例 (VM)、在 Kubernetes 上容器化或作为无服务器函数的 Oracle Cloud Infrastructure 上创建和运行 Go 应用。这些文章展示了如何使用 OCI DevOps 自动构建和部署这些 Go 应用。一个重要主题是如何使用来自 Go 应用的 OCI 服务,包括基于 OCI 运行的服务以及在其他位置运行的 Go 代码。讨论的 OCI 服务包括 Object Storage、Streaming、Key Vault 和 Autonomous Database。

逐条处理

为了跟上这些文章,读者至少应该对如何创建 Go 应用程序有基本的了解。假设读者可以访问自己的 Go 开发环境。一些示例和截图将特别提到 VS Code 作为开发工具。但是,也可以使用其他编辑器和 IDE。这些文章中介绍的 Go 代码以最简单的形式展示了一些机制,以实现最大清晰度和最小依赖性。读者不应该期望有意义的功能或生产就绪的代码。

本系列介绍如何在 OCI 上运行。要试用这些示例,读者需要有权访问 OCI 租户,并有权创建这些文章中讨论的 OCI 资源。所使用的大多数资源都位于始终免费套餐(计算实例、VCN、Autonomous Database、Object Storage、Logging、Resource Manager)中,或者具有用于每月有限使用的免费配额套餐(功能、API 网关、流处理、Vault、DevOps)。

这些系列的第一部分介绍了基于 Oracle Linux Cloud Developer 映像预配计算实例,为入站和出站网络活动打开计算实例,以及创建和运行提供 HTTP 请求并将应用程序生成的日志记录连接到 OCI Logging 的 Go 应用程序。第二部分涉及软件工程、使用 OCI DevOps 服务自动构建和部署应用。此服务用于存储 Go 源代码、构建应用程序可执行文件并将其存储为可部署构件,并将该构件部署到计算实例。本文还介绍了如何通过 OCI API 网关公开应用程序的 HTTP 端点。第三部分介绍了如何在 Go 中创建无服务器函数并将其部署到 OCI。引入了适用于 OCI 的 Go SDK —首先用于本地独立 Go 应用程序,然后用于函数,并利用资源主用户验证。此 SDK 用于与 OCI 对象存储服务交互,以创建存储桶以及写入和读取文件。

第四部分(您目前正在阅读)讨论了 Go 应用程序与 Oracle Database 之间的交互。这可以是本地或本地数据库、在某个云供应商的 IaaS 实例上运行的数据库或 OCI Autonomous Database。使用标准 Go 数据库/sql 程序包和 Oracle Database 驱动程序,并将所需的配置详细信息提供给驱动程序,事实证明,利用 Go 中的任何 Oracle Database 非常简单。第二部分讨论的 Go 应用 myserver 扩展到与 OCI 上的 Autonomous Database 实例和 OCI Object Storage 服务进行交互。该应用使用适用于 OCI 的 Go SDK 从对象存储上的存储桶中读取文件(并随后将其删除),并在 Autonomous Database 中根据其内容创建数据库记录。

本地 Go 应用程序与本地数据库通话

Go 语言支持与内置的关系数据库进行 SQL 交互。标准程序包 database/sql 包括用于连接到数据库、执行事务处理、取消正在进行的操作等的类型和函数。此软件包可用于以与某些 NoSQL 数据库(例如 MongoDB 和 Couchbase)相同的方式工作。

通过此程序包与数据库进行交互的 Go 应用程序不需要具有特定数据库产品的技术实施详细信息。这些详细信息通常在该数据库的驱动程序中实现。应用程序将导入要连接到的数据库所需的驱动程序,并告知标准程序包数据库/SQL 要使用的驱动程序以及数据库的连接详细信息。无论具体数据库技术如何,与数据库的大多数交互都是相同的;记录是通过 SQL DML 语句创建、更新和删除的,记录是通过 SQL 查询检索的。数据库的整体过程相同,但确切的 SQL 方言可能会有所不同。这可能是很容易在不同的数据库产品之间移动 Go 应用程序的唯一真正障碍。

本节讨论的代码位于本文系列附带的代码资料档案库的目录 /applications/go-orcl-db 中。

Go 应用程序执行 SQL - DDL、DML 和查询

从 Go 应用程序使用 Oracle Database 时,最简单的事情是查询单个行。此操作所需的代码如下所示:package main

package main


import (
"database/sql"
"fmt"
)

func sqlOperations(db *sql.DB) {
var queryResultColumnOne string
row := db.QueryRow("SELECT to_char(systimestamp,'HH24:MI:SS') FROM dual")
err := row.Scan(&queryResultColumnOne)
if err != nil {
  panic(fmt.Errorf("error scanning query result from database into target variable: %w", err))
}
fmt.Println("The time in the database ", queryResultColumnOne)
}

import 语句用于使数据库/sql 程序包可用。使用指向 sql.DB 的句柄,可以轻松执行 SQL 查询 (QueryRow),并将结果扫描到本地变量中。除了特定的 SQL 语句,该语句与 Oracle 特定的 systimestamp 不同,它非常简单、直接且独立于数据库品牌。

目前,我们不要详细说明 db 参数的来源。过一会儿,我们将讨论数据库驱动程序,这就是一切将要揭示的地方。

一个稍微有趣的功能,它创建表、插入记录、查询记录、创建到更多记录,然后查询所有行,最后删除表显示在此处。您可以在代码资料档案库中的 oracle-database-client-app.go 文件中找到此代码。

package main


import (
"database/sql"
"fmt"
)

const createTableStatement = "CREATE TABLE TEMP_TABLE ( NAME VARCHAR2(100), CREATION_TIME TIMESTAMP DEFAULT SYSTIMESTAMP, VALUE  NUMBER(5))"
const dropTableStatement = "DROP TABLE TEMP_TABLE PURGE"
const insertStatement = "INSERT INTO TEMP_TABLE ( NAME , VALUE) VALUES (:name, :value)"
const queryStatement = "SELECT name, creation_time, value FROM TEMP_TABLE

func sqlOperations(db *sql.DB) {
_, err := db.Exec(createTableStatement)
handleError("create table", err)
defer db.Exec(dropTableStatement) // make sure the table is removed when all is said and done
stmt, err := db.Prepare(insertStatement)
handleError("prepare insert statement", err)
sqlresult, err := stmt.Exec("John", 42)
handleError("execute insert statement", err)
rowCount, _ := sqlresult.RowsAffected()
fmt.Println("Inserted number of rows = ", rowCount)

var queryResultName string
var queryResultTimestamp time.Time
var queryResultValue int32
row := db.QueryRow(queryStatement)
err = row.Scan(&queryResultName, &queryResultTimestamp, &queryResultValue)
handleError("query single row", err)
if err != nil {
  panic(fmt.Errorf("error scanning db: %w", err))
}
fmt.Println(fmt.Sprintf("The name: %s, time: %s, value:%d ", queryResultName, queryResultTimestamp, queryResultValue))
_, err = stmt.Exec("Jane", 69)
handleError("execute insert statement", err)
_, err = stmt.Exec("Malcolm", 13)
handleError("execute insert statement", err)

// fetching multiple rows
theRows, err := db.Query(queryStatement)
handleError("Query for multiple rows", err)
defer theRows.Close()
var (
  name  string
  value int32
  ts    time.Time
)
for theRows.Next() {
  err := theRows.Scan(&name, &ts, &value)
  handleError("next row in multiple rows", err)
  fmt.Println(fmt.Sprintf("The name: %s and value:%d created at time: %s ", name, value, ts))
}
err = theRows.Err()
handleError("next row in multiple rows", err)
}

func handleError(msg string, err error) {
if err != nil {
  fmt.Println(msg, err)
  os.Exit(1)
}
}

此代码在本质上非常实用。除了 SQL 语句之外,没有特定于数据库的实施详细信息。似乎有一半的代码是错误处理。要理解这个代码是如何操纵数据库的,应该不会太难,除了还没有可使用的数据库,而且(因此)也没有驱动程序来建立连接和处理通信。让我们先运行本地数据库,然后将数据库驱动程序添加到 Go 应用程序来对此进行补救。

运行本地 Oracle Database

有许多不同的方法可以启动并运行本地 Oracle Database。我发现的最简单的方法是使用一个 Docker 容器映像,它允许我用一个非常简单和简单的语句运行本地数据库:

docker run -d -p 1521:1521 -e ORACLE_PASSWORD=TheSuperSecret1509! gvenzl/oracle-xe

这将运行 Oracle Database XE 21c 实例(至少,这是编写时执行的操作,当 21c 是最新的可用容器映像时),并将 SYS 和 SYSTEM 的密码设置为指示的值。该数据库位于 localhost 上的端口 1521 上。

Oracle 的 Gerald Venzl 维护了一系列 (Docker) 容器映像,这些映像运行 Oracle Database(XE 版本)的薄版,可免费使用(最多 20GB 数据,最多使用 2 个 CPU 线程和 2 GB RAM)。他在一篇名为 Gvenzl/oracle-XE:Oracle Database XE Docker 映像简介的文章中介绍了这些映像以及如何使用这些映像。

按照以下步骤验证本地 Oracle Database 是否已启动且正在运行:

  1. 使用 docker ps | grep gvenzl 查找容器标识符。然后在容器中打开一个 Bash shell:
    ```console
    
    docker exec -it <container identifier=""> /bin/bash
    ``` </container>
  2. 现在,连接到数据库并运行 SQL 语句:
    ```console
    
    sqlplus system/TheSuperSecret1509! as sysdba
    ```
  3. 在以下部分中创建要使用的用户(方案),例如无辜的演示用户:
    ```sql
    
    create user demo identified by demo default tablespace users temporary tablespace temp 
    /
    grant connect, resource to demo 
    /
    ```

现在,是时候从 Go 应用程序连接到此数据库了。

Note: You may be interested in installing the Oracle VS Code extension that allows making connections to Oracle Databases – local and remote – browse their contents and interact with them similar to SQL Developer and other desktop tools.

为 Oracle Database 添加驱动程序

Oracle Database 没有官方的 Go 驱动程序,至少没有 Oracle 发布或认可的 Go 驱动程序。Go 数据库驱动程序的非官方列表包含四个 Oracle Database 条目。有三个需要安装 Oracle 客户端库,一个不需要。让我们首先使用最后一个,称为 go-ora 的驱动程序,它本身处理与数据库的所有通信。有关 go-ora 的详细信息,请访问 GitHub 上的 go-ora 主页。之后,我们还将了解 Godror,这是一个需要安装库的驱动程序,也是 Oracle Database 驱动程序中看起来最突出的驱动程序。

go-ora 驱动程序可以通过以下方式添加到 Go 应用程序:

go get github.com/sijms/go-ora/v2

这将下载软件包并将一个 require 条目添加到 go.mod 文件中。对我来说,看起来像这样:

module oracle-database-client


go 1.16

require (
github.com/sijms/go-ora/v2 v2.4.16 // indirect
)

在与 oracle-database-client-app.go 文件位于同一目录中的名为 pure-oracle-database-client.go 的新文件中(尽管 Go 并不真正关心该名称),以下代码通过 go-ora 处理与本地 Oracle Database 的交互。导入驱动程序包,对 sql.Open 的调用(该调用隐式引用 oracle)选择 go-ora 作为选择的驱动程序。

参数 dbParams 包含配置设置(包括用户名和密码)、数据库主机和端口以及用于建立连接的服务的名称的映射。连接字符串由这些元素组成,用于对 sql.Open 的调用。后续对 db.Ping 的调用是第一次尝试真正与数据库建立通信。当此调用成功时,我们已准备好执行一些实际的数据库操作。

package main


import (
"database/sql"
"fmt"
"net/url"
_ "github.com/sijms/go-ora/v2"
)

func GetSqlDBWithPureDriver(dbParams map[string]string) *sql.DB {
connectionString := "oracle://" + dbParams["username"] + ":" + dbParams["password"] + "@" + dbParams["server"] + ":" + dbParams["port"] + "/" + dbParams["service"]
db, err := sql.Open("oracle", connectionString)
if err != nil {
  panic(fmt.Errorf("error in sql.Open: %w", err))
}
err = db.Ping()
if err != nil {
  panic(fmt.Errorf("error pinging db: %w", err))
}
return db
}

连接和运行

数据库正在运行,我们有一个与纯 Oracle Database 驱动程序配合使用的函数。现在,让我们返回到 oracle-database-client-app.go 并将其绑定在一起。

在此文件中添加函数 main。它调用 GetSqlDBWithPureDriver 以使用映射 localDB 中定义的数据库连接详细信息创建 sql.DB 实例。修改这些值以与数据库配置保持一致。函数调用使用 *sql.DB 设置 db 变量,该变量可用于进一步执行 SQL 操作。

完成所有数据库交互后,应关闭连接以释放资源。为确保完成此操作,在调用 GetSqlDBWithPureDriver 后立即添加函数 main 中的延迟,并调用 db.Close()。对函数 sqlOperations 的调用传递了 db,让我们看看前面讨论的两个部分,其中数据库真正与之交互。

var localDB = map[string]string{

"service":  "XE",
"username": "demo",
"server":   "localhost",
"port":     "1521",
"password": "demo",
}

func main() {
db := GetSqlDBWithPureDriver(localDB)
defer func() {
  err := db.Close()
  if err != nil {
      fmt.Println("Can't close connection: ", err)
  }
}()
sqlOperations(db)
}

使用 go run *.go 从命令行运行应用程序。输出如下所示:

go run *.go

Inserted number of rows =  1
The name: John, time: 2022-04-25 05:31:02.489289 +0000 UTC, value:42 
The name: John and value:42 created at time: 2022-04-25 05:31:02.489289 +0000 UTC 
The name: Jane and value:69 created at time: 2022-04-25 05:31:02.506039 +0000 UTC 
The name: Malcolm and value:13 created at time: 2022-04-25 05:31:02.509098 +0000 UTC

使用 GoDrOr 驱动程序

go-ora 的流行替代方案是 Go 软件包 godror(以前称为 goracle,但由于商标问题而重命名 - Oracle Corporation 不希望任何人在其名称中使用 oracle)。此程序包还提供了一个 Oracle Database 驱动程序,当为 oracle 或 godror 执行 sql.Open 时,数据库/SQL 可以使用该驱动程序。与 go-ora 不同,此软件包要求在运行 Go 应用程序的系统上安装 Oracle Instant Client 库。

  • 安装 Oracle Instant Client 库

    GoDrOr 驱动程序使用 Oracle Database Programming Interface for C (ODPI-C)。它是一个由 Oracle Corporation 维护的 C 代码开源库,可简化 Oracle Database 驱动程序和用户应用常用 Oracle Call Interface (OCI) 功能的使用。在使用 GoDrOr 时,我们不需要安装 ODPI-C,甚至不需要意识到它的存在。但是,运行 Go 应用程序(包括驱动程序)的环境需要包含 Oracle 客户端库。

    最简单的 Oracle 客户端是免费的 Oracle Instant Client(请参阅 Oracle Instant Client 概览页面)。只有“基本”或“基本光”包是必需的。在任何 Oracle Database 安装或 Oracle Client 完整安装中均提供 Oracle Client 库。《Oracle Database Documentation for Release 21c – Database Client Installation Guide for Linux – Installing Oracle Instant Client 》中提供了有关 Linux 的详细安装说明。

    ODPI-C 在运行时动态加载可用的 Oracle Client 库。将在 ODPI-C 库(或应用程序二进制文件)所在的目录中搜索 Oracle Client 库。如果未找到它们,则会在标准操作系统搜索路径中搜索它们,例如 Windows 上的 PATH 或 Linux 上的 LD_LIBRARY_PATH。最后,在 Windows 以外的平台上,还会搜索 $ORACLE_HOME/lib。有关确保 ODPI-C 可以找到 Oracle 客户端库的详细信息,请查看 ODIP-C GitHub Home – ODPI-C Installation

  • 修改 Go 应用程序以使用 GoDrOr

    我们必须对应用程序进行更改,以便从使用 go-ora 切换到 godror 是最小的。

    首先,godror 驱动程序被添加到 Go 应用程序中:

    github.com/godror/godror

    这将下载软件包并将一个 require 条目添加到 go.mod 文件中。

    接下来,在同一应用程序目录中创建一个名为 godror-based-oracle-database-client.go 的新文件,该文件与 pure-oracle-database-client.go 非常相似,后者包含通过 go-ora 驱动程序进行连接的详细信息。

    此新文件的内容:

    package main
    
    
    import (
    "database/sql"
    "fmt"
    
    _ "github.com/godror/godror"
    )
    
    func GetSqlDBWithGoDrOrDriver(dbParams map[string]string) *sql.DB {
    var db *sql.DB
    var err error
    connectionString := "oracle://" + dbParams["username"] + ":" + dbParams["password"] + "@" + dbParams["server"] + ":" + dbParams["port"] + "/" + dbParams["service"]
    db, err = sql.Open("oracle", connectionString)
    err = db.Ping()
    if err != nil {
      panic(fmt.Errorf("error pinging db: %w", err))
    }
    return db
    }

    与 go-ora 的导入相比,godror 包的导入是不同的。其余代码与之前完全相同。

    Note: when we use the Oracle Wallet and change to encrypted communications with the Autonomous Database, there will be more differences between the code used with the two drivers.

    最后,为了使应用程序停止使用 go-ora 并开始使用 godror,我们只需要注释掉或删除一行并在函数 main 中添加另一行,调用 GetSqlDBWithGoDrOrDriver:

    func main() {
    
    //db := GetSqlDBWithPureDriver(localDB)
    db := GetSqlDBWithGoDrOrDriver(localDB)

    使用 go run *.go 重新运行应用程序,您会发现与以前相同的输出。现在涉及 Oracle Instant Client 的事实并不明显。行为显然没有改变,即使可能存在非功能性差异,例如某些操作的性能。

数据库事务处理管理

从我们之前的讨论中,不明显的是我们从未真正将数据提交到数据库。所有 SQL 操作都发生在单个数据库会话中。创建和删除表的两个 DDL 操作隐式提交了事务处理,但未提交任何 INSERT 语句。某些数据库具有自动提交设置(有些甚至是默认设置),可以将每个 DML 操作转变为自动提交的事务处理。与 Oracle Database 不同。为了提交对数据库记录所做的更改,必须显式提交这些更改,或者回滚以防其持久影响毕竟不可取。在 Go 应用程序中,我们可以显式处理数据库事务处理,方法是:在数据库中开始事务处理 (sql.Tx),对该事务处理执行 DML 语句,最后提交或回退事务处理。例如:

ctx := context.Background()

tx, err := db.BeginTx(ctx, nil)
err = tx.ExecContext(ctx, DMLStatement, ... bind parameter values)
err = tx.ExecContext(ctx, AnotherDMLStatement, ... bind parameter values)
err = tx.Commit() // or tx.Rollback()

Go Application 与 OCI Autonomous Database 通话

让 Go 应用程序与本地(或任何传统上连接的)Oracle Database 通信不是那么困难。配置为使用 Oracle Wallet 的客户端进行加密通信的数据库(例如 OCI 上的 Oracle Autonomous Database 实例)不再难与之交互。我们需要扩展代码以使用 Oracle Wallet 文件,当然还需要运行 Autonomous Database 实例并获取关联的 Oracle Wallet。

在 OCI 上运行免费的 Autonomous Database

运行 Autonomous Database 实例要比运行本地数据库简单得多。可以通过多种方式(包括通过 OCI CLI 和 Terraform)创建 ATP 实例,但您第一次使用的最简单方法可能是通过 OCI 浏览器控制台。

Note: Tim Hall provides a good description in his article Oracle Cloud : Autonomous Transaction Processing (ATP) – Create Service, and there are many more to be found.

让我们创建您始终免费的 ATP 实例:

在控制台的搜索框中键入自动内容,导航到 *Autonomous Database Features*,单击“Create Autonomous Database(创建自治数据库)”按钮。


关联文章 -4-nav-autonomous-db

在创建表单中,提供显示名称(可能为 go-on-oci-db)和数据库名称,选择“Transaction Processing(事务处理)”作为工作量类型,将“Always Free(始终免费)”切换为活动状态,为 ADMIN 提供密码(请记住这一点),从任何位置接受“Network Access Type Secure access(网络访问类型安全)”访问,并确保选中“Require 双向 TLS (mTLS) authentication(要求双向 TLS)验证 )”。


创建自治数据库

按“Create Autonomous Database(创建自治数据库)”按钮创建数据库后,会显示预配状态:


预配 atp

要使数据库可用,只需不到一分钟。

下载具有连接详细信息的数据库 Wallet

我们需要包含 mTLS 交互所需的 SSL 证书的数据库 wallet。下载 ATP 实例的 wallet。首先在 OCI 控制台的 ATP 页面中单击“DB Connection(数据库连接)”按钮,然后单击“Download wallet(下载 wallet)”。


下载 dbwallet

提供 wallet 的密码;以后读取 wallet 可能需要此密码。还要保留此密码,但对于本文中描述的步骤,我不需要此密码。

保存 zip 文件,我们很快就会用到。


downloadwallet2

在 Autonomous Database 中创建演示用户账户

您可能需要在自治数据库中创建演示用户账户。可通过以下步骤执行此操作:

  1. 在“ATP 详细信息”页面上,单击“数据库操作”按钮。以用户管理员身份进行连接,并使用在配置 ATP 时使用的密码。

  2. 在“Database Actions(数据库操作)”启动板中,单击磁贴 SQL。此时将打开 SQL 工作表。

  3. sqldeveloper
  4. 将以下语句粘贴到工作表中以及用于运行脚本的图标(或使用键盘上的 F5 按钮)。这些语句会在以下部分中创建一个要使用的用户(方案),就像我们在本地数据库中所做的那样:
    create user demo identified by thePassword1 default tablespace users temporary tablespace temp
    
    /
    grant connect, resource to demo 
    /
    ALTER USER DEMO QUOTA UNLIMITED ON DATA
    /

使用 ATP 和 Oracle Wallet 连接详细信息修改 Go 应用程序

如果要与之交互的 Oracle Database 需要使用 Oracle Wallet 连接到时,我需要将 Oracle Wallet 的文件系统位置传递给驱动程序。更准确地说,我需要指定包含文件 cwallet.sso 的目录的路径,该文件是 wallet 的一部分。Wallet 通常在 zip-archive 中提供。应提取此归档文件(或至少应提取此文件),包含该文件的目录的路径称为 walletLocation。此时,从 wallet zip 文件中提取 cwallet.sso 并将此文件移动到可从 Go 应用程序访问的位置 - 它甚至可能与 Go 应用程序本身位于同一目录中。这不是生产级应用的最佳实践,但就本文而言,这就足够了。

需要提供的自治数据库的连接详细信息由先前用于本地数据库的同一组元素组成。可以在 wallet zip 文件的 tnsnames.ora 文件中或 OCI 控制台的 ATP 数据库连接页上找到数据库服务名称,该名称为 service_name。服务器属性的值可用作这些位置中的主机。

收集属性后,可以在文件 oracle-database-client-app.go 中的 localDB 下添加以下映射定义:

var autonomousDB = map[string]string{
"service":        "k8j2fvxbaujdcfy_goonocidb_medium.adb.oraclecloud.com",
"username":       "demo",
"server":         "adb.us-ashburn-1.oraclecloud.com",
"port":           "1522",
"password":       "thePassword1",
"walletLocation": ".", // when the *.sso file has been moved into the application directory; otherwise provide the absolute directory path
}

使用 Driver go-ora 与 Autonomous Database 进行交互

需要对 go-ora 驱动程序配置进行一些扩展,以将 wallet 位置包含在连接字符串中并配置安全通信协议。

func GetSqlDBWithPureDriver(dbParams map[string]string) *sql.DB {

connectionString := "oracle://" + dbParams["username"] + ":" + dbParams["password"] + "@" + dbParams["server"] + ":" + dbParams["port"] + "/" + dbParams["service"]
if val, ok := dbParams["walletLocation"]; ok && val != "" {
  connectionString += "?TRACE FILE=trace.log&SSL=enable&SSL Verify=false&WALLET=" + url.QueryEscape(dbParams["walletLocation"])
}
db, err := sql.Open("oracle", connectionString)
...

要针对 Autonomous Database 运行应用并在云端执行 TEMP_TABLE 杂技,我们需要稍微更改主功能:

func main() {

db := GetSqlDBWithPureDriver(autonomousDB)
//db := GetSqlDBWithGoDrOrDriver(localDB)
...

即:将调用 GetSqlDBWithPureDriver 中的 localDB 引用替换为 autonomousDB。

现在使用 go run *.go 重新运行应用程序。对于本地数据库,结果与以前完全相同,但生成这些结果可能需要更长的时间,因为现在在每个数据库交互中都引入了延迟。

使用 Driver godror 与 Autonomous Database 进行交互

Godror 驱动程序使用与 go-ora 相比稍微不同的设置来处理 Oracle Wallet。扩展了文件 godror-based-oracle-database-client.go 中的函数 GetSqlDBWithGoDrOrDriver 来处理以下情况:

func GetSqlDBWithGoDrOrDriver(dbParams map[string]string) *sql.DB {

var db *sql.DB
var err error
if val, ok := dbParams["walletLocation"]; ok && val != "" {
  db, err = sql.Open("godror", fmt.Sprintf(`user="%s" password="%s"
  connectString="tcps://%s:%s/%s?wallet_location=%s"
     `, dbParams["username"], dbParams["password"], dbParams["server"], dbParams["port"], dbParams["service"], dbParams["walletLocation"]))
}
if val, ok := dbParams["walletLocation"]; !ok || val == "" {
  connectionString := "oracle://" + dbParams["username"] + ":" + dbParams["password"] + "@" + dbParams["server"] + ":" + dbParams["port"] + "/" + dbParams["service"]
  db, err = sql.Open("oracle", connectionString)
}
err = db.Ping()
...

要使用 Godror 驱动程序对 Autonomous Database 运行应用,并在云端执行 TEMP_TABLE 杂技,我们需要稍微更改主函数:

func main() {

//db := GetSqlDBWithPureDriver(autonomousDB)
db := GetSqlDBWithGoDrOrDriver(autonomousDB)
...

现在使用 go run *.go 重新运行应用程序。结果将再次与 go-ora 驱动程序完全相同,但似乎(至少在我的环境中)通过 go-ora 的操作比通过 godror 的相同操作快得多。

将 Go 应用与 Autonomous Database 通话部署到 OCI

代码资料档案库包含一个名为 data-service 的应用程序,位于目录 /applications/data-service 中。此应用程序是我们在本系列文章第一和第二篇文章中使用的 myserver 应用程序的扩展。该应用程序仍然像以前一样处理 HTTP 请求,这次也实现了简单的数据 API。使用 PUT、POST 和 DELETE 请求,应用程序可用于在 Oracle Database 中从名为 PEOPLE 的表中创建、更新和删除人员记录。使用 GET 请求,可以检索任何人的当前详细信息。

我们将首先简要介绍应用程序中的有趣元素,然后在本地运行。下一步是在计算实例中的 OCI 上运行此应用。您会发现,在 OCI 上部署时,具有 Autonomous Database 交互的应用程序没有什么特别之处。或者,至少直到本系列中的下一期,我们将使用 OCI Key Vault 来安全地保存 Oracle Wallet 详细信息,这要归功于基于实例主体的授权,应用程序可以在运行时检索。然而,目前,钱包包含在源代码存储库中,并在构建和部署管道中处理。这不是一个好的做法,将在下一篇文章中予以纠正。

部署应用后,我们确认是否可以通过直接访问计算实例来访问该应用。为了应用关于(而不是)直接公开服务的良好最佳实践,我们将 API 网关扩展到另外一个路由,导致数据服务,特别是其基于数据库的功能。

本节末尾我们在 OCI 上实现的最终情况如下所示:


devops-oci-vm

检查数据服务并为 ATP 实例配置

文件 data-server.go 在应用程序中是新增的。它包含与数据库进行交互以及处理对路径数据中包含的应用程序的任何 HTTP 请求的所有逻辑;DATA_PATH。DataHandler 函数的函数 main 中的注册集成了数据处理功能。

http.HandleFunc(DATA_PATH, DataHandler)

函数 main 还通过以下初始化步骤进行扩展:

func main() {

db := GetSqlDBWithGoDrOrDriver(autonomousDB)
defer func() {
  err := db.Close()
  if err != nil {
      fmt.Println("Can't close connection: ", err)
  }
}()
InitializeDataServer(db)
...

在 my-server 应用程序启动时,将创建数据库连接并设置数据服务器。应用程序使用 Godror 驱动程序。请注意,我们利用的是,作为部署目标的计算实例是在 Oracle Linux Cloud Developer 映像上创建(该系列的一部分)并预安装了 Oracle Instant Client 的这一事实。

要运行,需要添加的所有应用程序为:

  1. 将 cwallet.sso 文件复制到应用程序的根目录
  2. 在 data-server.go 中定义 Autonomous Database 连接详细信息

然后,您可以在本地运行应用程序,使用

go run *.go

应用程序将启动并报告职责。

在单独的终端窗口中,可使用 curl 语句与 Person API 交互。这些 HTTP 请求将导致创建两条记录 - 用于 Mickey Mouse 和 Bugs Bunny。米奇的记录更新一次。两条记录将检索一次。两者都被删除了。最终的 GET 请求不返回任何数据。

随意添加卷曲请求或不执行所有请求。例如,您可以在 SQL 工作表中检查 Person API 是否创建了预期的数据库记录。


SQL 工作表

curl -X "PUT" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse", "age":93, "comment": "Cartoon Character"}' localhost:8080/data


curl -X "PUT" -H "Content-Type: application/json" -d '{"name":"Bugs Bunny", "age":84, "comment": "an animated cartoon character created in the late 1930s by Leon Schlesinger Productions (later Warner Bros. Cartoons) and voiced originally by Mel Blanc."}' localhost:8080/data 

curl -X "POST" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse", "age":93, "comment": "Cartoon Character and Movie Star, created in 1928 by Walt Disney and first appearing in Steamboat Willie; he is the mascot of The Walt Disney Company. His partner is Minnie and he has a pet dog called Pluto "}' localhost:8080/data 

curl -X "GET"  localhost:8080/data?name=Mickey+Mouse 

curl -X "GET"  localhost:8080/data?name=Bugs+Bunny 

curl -X "DELETE" -H "Content-Type: application/json" -d '{"name":"Bugs Bunny"}' localhost:8080/data 

curl -X "DELETE" -H "Content-Type: application/json" -d '{"name":"Mickey Mouse"}' localhost:8080/data 

curl -X "GET"  localhost:8080/data?name=Mickey+Mouse

提交、推送和构建,引导部署和运行

myserver 应用程序的此变体已准备就绪,并且代码已在 OCI DevOps 代码资料档案库中,因为 GitHub 上的文章源资料档案库中的所有代码都已提交到本系列第二篇文章中的 OCI 代码资料档案库。但是,您添加了文件 cwallet.sso(Autonomous Database 实例的 Oracle Wallet),并且更新了文件 data-server.go 以提供数据库连接详细信息。在 OCI 上使用构建管道来构建和随后部署应用程序之前,首先需要添加新文件,提交更改的文件和此添加的文件,并将更改推送到 OCI DevOps 代码资料档案库。

在这些 git 添加、提交和推送操作之后,代码库 go-on-oci-repo 应该包含您修改的 cwallet.sso 和 data-service.go。

现在,当我们第一次讨论 OCI DevOps 构建管道时,您可以重用第 2 条中设置的构建管道构建服务器。但是,当前管道需要默认位置中的构建规范文件,这对于修改后的 myserver 应用程序不会执行。

  1. 在 OCI 控制台中打开构建管道构建 yserver 的详细信息页面。打开托管构建阶段的详细信息。单击“编辑”。

  2. 构建秒

  3. 将字段“构建”规范文件路径中的值更改为 /applications/data-service/build_spec.yaml,即修改后的构建规范以构建 myserver 的扩展版本。单击“保存”。

  4. 管道

  5. 启动构建运行。如果需要,请为参数 MYSERVER_VERSION 设置新版本。

管道将生成一个新构件 - 一个 zip 文件,其中包含从目录 /applications/data-service 中的 Go 源构建的可执行文件,并包含 wallet 文件和网站子目录。管道将触发将构件带到计算实例的部署管道,将应用程序复制到 /tmp/yourserver 目录,然后运行应用程序。它开始在部署管道参数 HTTP_SERVER_PORT 指定的端口上监听 HTTP 请求(如果未设置该参数,则在 8080 上)。

您可以访问 VM 的公共 IP 地址上的 Person API(如果仍公开):

curl -X "GET"  <public ip="" for="" compute="" instance="">:8095/data?name=Mickey+Mouse

 </public>

您可以在 API 网关上创建路由,以提供对 Person API 的正确公共访问。确保将 API 处理的所有方法添加到路由定义。


管道

更新部署后,可在 https:// /my-api/person?name=Mickey+Mouse 上使用 Person API。


计算实例

Curl 和 Postman 等其他 HTTP 工具可用于与 API 进行交互,使用所有方法来创建,更新,检索和删除人员记录。


浏览器

在 OCI 上运行应用,与 Autonomous Database 和 Object Storage 服务交互

本文的最后一步结合了与 OCI Object Storage 服务(在上一篇文章中介绍)的交互与单个 Go 应用程序中 Autonomous Database 实例的操作,该应用程序首先在本地运行,然后部署到 OCI 中的计算实例上并执行,然后通过 API 网关公开。提供的功能:发送 HTTP GET 请求,其中包含对象名称和对象存储上的存储桶;对象应是一个 JSON 文件,其中包含以下格式的人员数据:

[

{
  "name": "Jasper",
  "age": 19,
  "comment": "Haute Couture"
},
{
  "name": "James",
  "age": 3,
  "comment": "Golden retriever"
}
]

将读取该文件,并在 Autonomous Database 的表 PEOPLE 中为每个 JSON 条目创建记录。

要运行以下命令,需要将所有内容添加到应用程序:

  1. 将 cwallet.sso 文件复制到应用程序的根目录
  2. 在 data-server.go 中定义 Autonomous Database 连接详细信息
  3. 编辑 my-server.go –为 compartmentOCID 设置正确的值
  4. 将文件 website/sample-persons.json 上载到对象存储服务上的存储桶(可随时编辑文件或上载具有类似内容的其他文件)

然后,您可以在本地运行应用程序,使用

go run *.go

应用程序将启动并报告职责。

在单独的终端窗口中,可以使用 curl 语句与新的 Persons 文件处理器 API 进行交互。HTTP 请求应传递存储桶的名称以及包含要处理的 JSON 数据的对象。该服务将提取文件,解析其内容,并在自治数据库中创建或更新 PEOPLE 表的记录。

curl "localhost:8080/people?objectName=sample-persons.json&bucketName=the-bucket"

通过调用数据 API,您可以检查数据记录:

curl localhost:8080/data?name=Martha

可以在 SQL Developer 工作表中执行相同的操作:


已处理的人员

您可能对 PeopleJSONProcessor 函数感兴趣,该函数处理表“人员”中的记录创建(或更新)。它使用 Oracle 特定的批量 DML 方法(由 Godror 驱动程序支持的语法),该方法将传入每个绑定参数的值数组,并在单个 DML 语句中创建所有记录。非常高效。

func PeopleJSONProcessor(peopleJson []byte) {

var persons []Person
json.Unmarshal(peopleJson, &persons)
nameVals := make([]string, len(persons))
ageVals := make([]int, len(persons))
descriptionVals := make([]string, len(persons))
for i, person := range persons {
  ageVals[i] = person.Age
  nameVals[i] = person.Name
  descriptionVals[i] = person.JuicyDetails
}

database.Exec(`MERGE INTO PEOPLE t using (select :name name, :age age, :description description from dual) person
  ON (t.name = person.name )
  WHEN MATCHED THEN UPDATE SET age = person.age, description = person.description
  WHEN NOT MATCHED THEN INSERT (t.name, t.age, t.description) values (person.name, person.age, person.description) `,
  nameVals, ageVals, descriptionVals)
}

现在,让我们将此应用程序带到 OCI,再到上一节中还使用的计算实例。准备时需要采取一些步骤:

  1. 编辑文件 object-processor.go:将 const RUN_WITH_INSTANCE_PRINCIPAL_AUTHENTICATION 的值从 false 更改为 true(在本地运行时此标志为 false,在使用实例主用户验证和授权的 OCI 中的计算实例上运行时,此标志为 true)
  2. Git 操作:添加 cwallet.sso 并提交此新文件以及更改的文件 my-server.go、object-processor.go 和 data-service.go;将提交推送到 OCI 代码资料档案库
  3. 确保存在 IAM 策略,该策略允许动态组联机实例读取区间中的对象;这样,在 VM 上运行的应用程序可以调用对象存储服务来读取包含人员记录的 JSON 文件。策略语句可以读取:允许动态组关联实例读取区间关联中的对象

在这些 git 添加、提交和推送操作之后,代码库 go-on-oci-repo 应该包含您修改的 cwallet.sso 和 data-service.go。

现在,您可以重用我们以前使用的构建管道构建服务器。正如我们之前所做的那样,我们必须更新对构建规范文件的引用。

  1. 在 OCI 控制台中打开构建管道构建服务器的详细信息页面
  2. 打开托管构建阶段的详细信息
  3. 单击编辑
  4. 将字段“构建”规范文件路径中的值更改为 /applications/people-file-processor/build_spec.yaml,即修改后的构建规范以构建 myserver 的扩展版本
  5. 单击保存

构建规范

启动构建运行。如果需要,请为参数 MYSERVER_VERSION 设置新版本。

管道将生成一个新构件 - 一个 zip 文件,其中包含从目录 /applications/people-file-processor 中的 Go 源构建的可执行文件,并包含 wallet 文件和网站子目录。管道将触发将构件带到计算实例的部署管道,将应用程序复制到 /tmp/yourserver 目录,然后运行应用程序。它开始在部署管道参数 HTTP_SERVER_PORT 指定的端口上监听 HTTP 请求(如果未设置该参数,则在 8080 上)。

您可以访问 VM 公共 IP 地址上的 API(如果该 IP 地址仍然暴露)。最好在 API 网关上添加路由以公开对人事文件处理器的访问:

  1. 导航到 api-gateway 的详细信息
  2. 打开选项卡部署
  3. 单击编辑
  4. 打开路由的第二步并添加新路由。

将路径定义为 /personnel-file-handler。应支持 GET 方法。路由的类型是 HTTP。URL 为:http://<Public IP for Compute Instance>:8095/people。检查部署管道 deploy-myserver-on-go-app-vm 上的 HTTP_SERVER_PORT 的值。如果未设置为 8095,则修改 URL 值以对应用程序使用正确的端口号。


中央处理器

Next ,然后按 Save changes 。刷新 API 网关的部署需要一些时间。读取对象存储桶中的文件、处理 JSON 内容并在 Autonomous Database 实例中的表 PEOPLE 中为此文件中的条目创建记录的服务可以在触发后,通过对 API Gateway>/my-api/personnel-file-handler 的 https://<public endpoind 的简单 HTTP GET 请求?objectName=sample-persons.json&bucketName=the-bucket。

可以通过从同一数据库中的同一表中读取记录的人员 API 检查进行调用的效果:https://<public endpoind of API Gateway>/my-api/person?name=Jasper。

下图显示了在 OCI 上部署的端到端情况。


version2

图片中未显示的内容,需要注意的是:

  • 与应用程序一起部署的 Oracle Wallet 文件(在从代码资料档案库中的源构建的构件中)
  • 对应用程序中区间的硬编码引用
  • 授予对计算实例读取对象的权限的策略

在接下来的文章中,我们将了解 OCI Key Vault,它提供了一种更好的方法来存储 Oracle Wallet 并使其在部署时(甚至运行时)可供应用使用。

总结

本文演示了如何从 Go 应用(传统应用或自治应用)与 Oracle Database 进行交互。我们已经了解了如何使用驱动程序和可能支持库来连接 Go 应用到本地 Oracle Database 以及 Autonomous Database,以及如何从舒适的 Go 应用中对这些数据库执行 DDL 和 DML SQL 操作。讨论了使用 Oracle Wallet 正确管理(自治)数据库身份证明的问题。在 OCI Compute 实例上运行的应用程序,通过 OCI Go SDK 与对象存储服务进行交互,并操纵 Autonomous Database(通过 OCI DevOps 自动部署)提供了文章结尾。

本系列的第五篇和最后一篇文章添加了另外两个 OCI 服务,Go 应用程序可以与这些服务进行交互:OCI Streaming(一个允许在不同微服务和其他组件之间分离交互的大量流消息代理)和 OCI Key Vault,用于管理包含数据库凭据的 Oracle Wallet 等密钥。本文还介绍了虚拟机和无服务器功能旁边的第三种应用平台,采用托管的 OCI Kubernetes Enginer (OKE) 配置,并展示了 DevOps 部署管道如何以自动化方式将 Go 应用部署到 OKE。

注:为免疑义,本网页所用以下术语专指以下含义:

  1. 除Oracle隐私政策外,本网站中提及的“Oracle”专指Oracle境外公司而非甲骨文中国。
  2. 相关Cloud或云术语均指代Oracle境外公司提供的云技术或其解决方案。

此页面内容为机器翻译。