Nesta era de microsserviços e desenvolvimento ágil, a necessidade de APIs flexíveis e eficientes é mais importante do que nunca. Hoje, gostaria de compartilhar um projeto interessante que desenvolvi recentemente: uma API RESTful personalizável em Go (Golang) que permite a realização de consultas em um banco de dados Oracle.

Go, uma linguagem de programação criada no Google, é conhecida por sua simplicidade e eficiência. Seu suporte integrado para a manipulação de concorrência e sua velocidade tornam-na uma escolha ideal para o desenvolvimento de APIs. Além disso, sua sintaxe clara e sua robusta biblioteca padrão ajudam a tornar o código mais legível e manutenível.

Começamos nosso programa importando as bibliotecas necessárias. O pacote database/sql é usado para interagir com o banco de dados Oracle e o pacote github.com/gorilla/mux é usado para o roteamento HTTP.

Definimos uma estrutura chamada App que encapsula um roteador mux.Router e uma conexão com o banco de dados sql.DB. A função Initialize é usada para estabelecer a conexão com o banco de dados Oracle e para inicializar o roteador.

Em seguida, definimos nossas rotas com a função initializeRoutes e criamos a função getTableData para ser o manipulador da nossa rota /api/{table}.

A getTableData constrói uma string de query SQL com base nos parâmetros fornecidos na URL, como o nome da tabela, cláusulas de seleção, filtragem, ordenação e agrupamento. Depois, executa a consulta e retorna os resultados no formato solicitado pelo usuário: JSON, CSV ou HTML Table. Isto é conseguido com as funções rowsToCSV, rowsToHTMLTable e rowsToJSON.

Embora esse código tenha suas vantagens, ele também tem suas limitações. Atualmente, o código não implementa nenhuma forma de autenticação ou autorização e não protege contra ataques de injeção SQL. Portanto, recomenda-se implementar essas características antes de usar o código em um ambiente de produção.

package main

import (
    "io/ioutil"
	"database/sql"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
    "strings"
    "flag"
	"log"
	"os"

	"github.com/gorilla/mux"
	_ "github.com/godror/godror"
	"html/template"
)

type App struct {
	Router *mux.Router
	DB     *sql.DB
}

var (
	Debug *log.Logger
)

func (a *App) Run(addr string) { 
	http.ListenAndServe(addr, a.Router)
}

func (a *App) Initialize(user, password, host, port, dbname string) {
    // Constrói a string de conexão usando os argumentos fornecidos
    connectionString := fmt.Sprintf("%s/%s@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=%s)(PORT=%s)))(CONNECT_DATA=(SERVICE_NAME=%s)))", user, password, host, port, dbname)

    var err error
    a.DB, err = sql.Open("godror", connectionString)
    if err != nil {
        panic(err)
    }

    a.Router = mux.NewRouter()
    a.initializeRoutes()
}


func (a *App) initializeRoutes() {
	a.Router.HandleFunc("/api/{table}", a.getTableData).Methods("GET")
}

func (a *App) getTableData(w http.ResponseWriter, r *http.Request) {
	table := mux.Vars(r)["table"]
	params := r.URL.Query()

	selectClause := params.Get("$select")
	filterClause, err := url.QueryUnescape(params.Get("$filter"))
	orderByClause, err := url.QueryUnescape(params.Get("$orderby"))
	groupByClause, err := url.QueryUnescape(params.Get("$groupby"))
	outputFormat := params.Get("$output")

	query := buildQuery(table, selectClause, filterClause, orderByClause, groupByClause)

	rows, err := a.DB.Query(query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	switch outputFormat {
	case "csv":
		w.Header().Set("Content-Type", "text/csv")
		cw := csv.NewWriter(w)
		err = rowsToCSV(cw, rows)
	case "table":
		w.Header().Set("Content-Type", "text/html")
		err = rowsToHTMLTable(w, rows)
	default: // Default to JSON
		w.Header().Set("Content-Type", "application/json")
		err = rowsToJSON(w, rows)
	}

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func buildQuery(table, selectClause, filterClause, orderByClause, groupByClause string) string {
	if selectClause == "" {
		selectClause = "*"
	}
	query := fmt.Sprintf("SELECT %s FROM %s", selectClause, table)

	if filterClause != "" {
		filterClause = odataToSQL(filterClause)
		query = fmt.Sprintf("%s WHERE %s", query, filterClause)
	}

	if groupByClause != "" {
		query = fmt.Sprintf("%s GROUP BY %s", query, groupByClause)
	}

	if orderByClause != "" {
		query = fmt.Sprintf("%s ORDER BY %s", query, orderByClause)
	}

	return query
}

func odataToSQL(filter string) string {
    filter = strings.Replace(filter, " eq ", " = ", -1)
    filter = strings.Replace(filter, " ne ", " != ", -1)
    filter = strings.Replace(filter, " gt ", " > ", -1)
    filter = strings.Replace(filter, " lt ", " < ", -1)
    filter = strings.Replace(filter, " ge ", " >= ", -1)
    filter = strings.Replace(filter, " le ", " <= ", -1)
    filter = strings.Replace(filter, " and ", " AND ", -1)
    filter = strings.Replace(filter, " or ", " OR ", -1)
    filter = strings.Replace(filter, " not ", " NOT ", -1)
    return filter
}

func rowsToCSV(cw *csv.Writer, rows *sql.Rows) error {
    // Fetch column names
    columns, err := rows.Columns()
    if err != nil {
        return err
    }

    // Write the column names as the first row
    if err := cw.Write(columns); err != nil {
        return err
    }

    // Prepare a slice to hold the row data
    values := make([]sql.RawBytes, len(columns))
    scanArgs := make([]interface{}, len(values))

    for i := range values {
        scanArgs[i] = &values[i]
    }

    // Iterate over the rows, writing each one to the CSV writer
    for rows.Next() {
        err := rows.Scan(scanArgs...)
        if err != nil {
            return err
        }

        writeValues := make([]string, len(values))
        for i, v := range values {
            writeValues[i] = string(v)
        }

        if err := cw.Write(writeValues); err != nil {
            return err
        }
    }

    cw.Flush()

    if err := rows.Err(); err != nil {
        return err
    }

    if err := cw.Error(); err != nil {
        return err
    }

    return nil
}



func rowsToHTMLTable(w http.ResponseWriter, rows *sql.Rows) error {
	columns, err := rows.Columns()
	if err != nil {
		return err
	}

	// Prepare a slice to hold the row data
	values := make([]sql.RawBytes, len(columns))
	scanArgs := make([]interface{}, len(values))

	for i := range values {
		scanArgs[i] = &values[i]
	}

	var data []map[string]string
	for rows.Next() {
		err := rows.Scan(scanArgs...)
		if err != nil {
			return err
		}

		row := make(map[string]string)
		for i, v := range values {
			row[columns[i]] = string(v)
		}

		data = append(data, row)
	}

	tmpl, err := template.New("table").Parse(`
	<table>
	<thead>
	<tr>
	{{ range $key, $value := index . 0 }}
		<th>{{$key}}</th>
	{{ end }}
	</tr>
	</thead>
	<tbody>
	{{ range $i, $row := . }}
		<tr>
		{{ range $key, $value := $row }}
			<td>{{$value}}</td>
		{{ end }}
		</tr>
	{{ end }}
	</tbody>
	</table>
	`)
	if err != nil {
		return err
	}

	if err := tmpl.Execute(w, data); err != nil {
		return err
	}

	return nil
}


func rowsToJSON(w http.ResponseWriter, rows *sql.Rows) error {
    // Obtém os nomes das colunas
    columns, err := rows.Columns()
    if err != nil {
        return err
    }

    // Cria os slices para conter os valores das linhas
    values := make([]sql.RawBytes, len(columns))
    scanArgs := make([]interface{}, len(values))

    for i := range values {
        scanArgs[i] = &values[i]
    }

    // Cria um slice de maps para conter todas as linhas
    var data []map[string]string

    // Itera sobre as linhas
    for rows.Next() {
        // Lê os valores da linha
        err := rows.Scan(scanArgs...)
        if err != nil {
            return err
        }

        // Cria um map para conter a linha
        row := make(map[string]string)

        // Adiciona os valores ao map
        for i, v := range values {
            row[columns[i]] = string(v)
        }

        // Adiciona o map ao slice de maps
        data = append(data, row)
    }

    // Converte o slice de maps para JSON
    jsonData, err := json.Marshal(data)
    if err != nil {
        return err
    }

    // Escreve o JSON na resposta
    w.Write(jsonData)

    return nil
}

func InitializeLogger(debugMode bool) {
	if debugMode {
		Debug = log.New(os.Stdout, "DEBUG: ", log.Ldate|log.Ltime|log.Lshortfile)
	} else {
		Debug = log.New(ioutil.Discard, "", 0) // When not in debug mode, discard debug logs
	}
}


func main() {
	args := os.Args
	debugMode := false

	for _, arg := range args {
		if strings.HasPrefix(arg, "debug=") {
			debugMode = strings.TrimPrefix(arg, "debug=") == "1"
		}
	}
	
	InitializeLogger(debugMode)
	Debug.Println("This is a debug log message.") 
	a := App{}
	a.Initialize("username", "password", "host", "port", "service_name")
	a.Run(":8080")
}

Exemplo Prático

Suponha que tenhamos uma tabela chamada ‘vendas’ com as colunas ‘ano’, ‘produto’ e ‘quantidade’. Podemos consultar esta tabela da seguinte maneira:

GET /api/venda/api/vendas?$select=ano,produto,quantidade&$filter=ano%20eq%202020&$orderby=produto&$output=table

Output em table

ANOPRODUTOQUANTIDADE
2020Celular210
2020Laptop150

Output em Json
[
{"ANO":"2020","PRODUTO":"Celular","QUANTIDADE":"210"},
{"ANO":"2020","PRODUTO":"Laptop","QUANTIDADE":"150"}
]

Output em CSV

ANO,PRODUTO,QUANTIDADE
2020,Celular,210
2020,Laptop,150

Em resumo, construímos uma API RESTful personalizável em Go que se conecta a um banco de dados Oracle e retorna os resultados das consultas em três formatos diferentes. Este projeto destaca a flexibilidade e a eficiência de Go para o desenvolvimento de APIs, enquanto mostra uma maneira poderosa e flexível de interagir com bancos de dados Oracle.

Sobre o autor

Pós graduado em Gestão de Projetos em Tecnologia da Informação pela UNIASSELVI.
Esposo e Pai, curto atividades ao ar livre (Bike, SUP, Natação, Caminhar, Brincar no campo)

Atua com Banco de Dados Oracle desde de 2007. Atualmente é DBA Senior na FLUIDATA Serviços em Banco de dados (www.fluidata.com.br)

Principais atividade Banco de dados:

Implementação, migração, gerenciamento e suporte a produtos Oracle (8i, 9i, 10g, 11g, 12c, 18c, 19c RAC), multiplataforma;
Implementação, migração, gerenciamento e suporte a produtos Microsoft SQL Server (2008 - 2019);
Implementação, migração, gerenciamento e suporte a produtos PostgreSQL (9.3 - 14);
Monitoramento de ambientes 24×7;
Backup e Recovery;
Performance e Tuning;
Alta disponibilidade (HA);
EM database/grid/cloud control;
Conversão de databases;
Standby database / Oracle Data Guard;

Certificações:

Oracle Cloud Infrastructure 2019 Certified Architect AssociateOracle Cloud Infrastructure 2019 Certified Architect Associate
Oracle Database 12c Administrator Certified ProfessionalOracle Database 12c Administrator Certified Professional
Exadata Database Machine Models X2-2 and X2-8 Technology Support SpecialistExadata Database Machine Models X2-2 and X2-8 Technology Support Specialist
Oracle Database 11g Support SpecialistOracle Database 11g Support Specialist
OCP 11g - Oracle Certified Professional AdministratorOCP 11g - Oracle Certified Professional Administrator
OPN Certified Specialist 10g - PartnerNetwork Certified SpecialistOPN Certified Specialist 10g - PartnerNetwork Certified Specialist
Oracle Database 10g Real Applications Clusters AdministratorCertified ExpertOracle Database 10g Real Applications Clusters AdministratorCertified Expert
Oracle Database 10g: Managing Oracle on Linux Certified ExpertOracle Database 10g: Managing Oracle on Linux Certified Expert
OCP 10g - Oracle Certified Professional AdministratorOCP 10g - Oracle Certified Professional Administrator

Principais atividades DEVOPS:

PHP
ASP.net
C#
Docker
Golang
C++
Delphi
Python
HTML5
JavaScript

Você também pode gostar: