Escreva a API do banco

Concluído

Agora que construímos a lógica central do banco online, vamos criar uma API Web para testá-la a partir de um navegador (ou até mesmo da linha de comando). Por enquanto, não usaremos um banco de dados para persistir dados, então teremos que criar uma variável global para armazenar todas as contas na memória.

Além disso, vamos pular a parte de teste para evitar manter este guia por muito tempo. Idealmente, você seguiria a mesma abordagem que seguimos ao criar o pacote principal para escrever testes antes do código.

Configurar uma conta na memória

Em vez de usar um banco de dados para persistir dados, usaremos um mapa de memória para as contas que criaremos quando o programa for iniciado. Além disso, usaremos um mapa para acessar as informações da conta usando o número da conta.

Vá para o $GOPATH/src/bankapi/main.go arquivo e adicione o seguinte código para criar a variável global accounts e inicializá-la com uma conta. (Este código é semelhante ao que fizemos quando criamos os testes anteriormente.)

package main

import (
    "github.com/msft/bank"
)

var accounts = map[float64]*bank.Account{}

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }
}

Certifique-se de $GOPATH/src/bankapi/que está no local. Execute o programa com go run main.go para se certificar de que você não tem erros. O programa não faz mais nada por enquanto, então vamos adicionar a lógica para criar uma API da Web.

Expor o método de instrução

Criar uma API Web no Go é fácil, como você viu em um módulo anterior. Continuaremos a usar o net/http pacote. Também usaremos as HandleFunc funções e ListenAndServe para expor pontos de extremidade e iniciar o servidor. A HandleFunc função requer um nome para o caminho da URL que você deseja expor e o nome de uma função com a lógica para esse ponto de extremidade.

Vamos começar expondo a funcionalidade de imprimir o extrato de uma conta. Copie e cole a seguinte função em main.go:

func statement(w http.ResponseWriter, req *http.Request) {
    numberqs := req.URL.Query().Get("number")

    if numberqs == "" {
        fmt.Fprintf(w, "Account number is missing!")
        return
    }

    if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid account number!")
    } else {
        account, ok := accounts[number]
        if !ok {
            fmt.Fprintf(w, "Account with number %v can't be found!", number)
        } else {
            fmt.Fprintf(w, account.Statement())
        }
    }
}

O primeiro destaque da statement função é que ele está recebendo o objeto para escrever uma resposta de volta para o navegador (w http.ResponseWriter). Ele também está recebendo o objeto de solicitação para acessar as informações da solicitação HTTP (req *http.Request).

Em seguida, observe que estamos usando a req.URL.Query().Get() função para ler um parâmetro da cadeia de caracteres de consulta. Este parâmetro é o número da conta que enviaremos através da chamada HTTP. Usaremos esse valor para acessar o mapa da conta e obter suas informações.

Como estamos recebendo dados do usuário, devemos incluir algumas validações para evitar uma falha. Quando sabemos que temos um número de conta válido, podemos fazer a chamada para o Statement() método e imprimir a string que ele retorna para o navegador (fmt.Fprintf(w, account.Statement())).

Agora, modifique sua main() função para que fique assim:

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }

    http.HandleFunc("/statement", statement)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

Se você não vir nenhum erro ou saída ao executar o programa (go run main.go), ele está funcionando corretamente. Abra um navegador da Web e digite a URL http://localhost:8000/statement?number=1001ou execute o seguinte comando em outro shell enquanto o programa estiver em execução:

curl http://localhost:8000/statement?number=1001

Deverá ver o seguinte resultado:

1001 - John - 0

Exponha o método de depósito

Vamos continuar usando a mesma abordagem para expor o método de depósito. Neste caso, queremos adicionar dinheiro à conta que temos na memória. Toda vez que chamamos o Deposit() método, o equilíbrio deve aumentar.

No programa principal, adicione uma deposit() função como a seguinte. A função obtém o número da conta da cadeia de caracteres de consulta, valida que a conta existe no mapa, valida que o valor a depositar é um número válido e, em accounts seguida, chama o Deposit() método.

func deposit(w http.ResponseWriter, req *http.Request) {
    numberqs := req.URL.Query().Get("number")
    amountqs := req.URL.Query().Get("amount")

    if numberqs == "" {
        fmt.Fprintf(w, "Account number is missing!")
        return
    }

    if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid account number!")
    } else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid amount number!")
    } else {
        account, ok := accounts[number]
        if !ok {
            fmt.Fprintf(w, "Account with number %v can't be found!", number)
        } else {
            err := account.Deposit(amount)
            if err != nil {
                fmt.Fprintf(w, "%v", err)
            } else {
                fmt.Fprintf(w, account.Statement())
            }
        }
    }
}

Observe que essa função segue uma abordagem semelhante para obter e validar os dados que recebe do usuário. Também estamos declarando e usando variáveis diretamente na if declaração. Finalmente, depois de adicionarmos alguns fundos à conta, imprimimos o extrato para ver o novo saldo da conta.

Agora, você deve expor um ponto de /deposit extremidade que chama a deposit função. Modifique sua main() função para ter esta aparência:

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }

    http.HandleFunc("/statement", statement)
    http.HandleFunc("/deposit", deposit)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

Se você não vir nenhum erro ou saída ao executar o programa (go run main.go), ele está funcionando corretamente. Abra um navegador da Web e digite a URL http://localhost:8000/deposit?number=1001&amount=100ou execute o seguinte comando em outro shell enquanto o programa estiver em execução:

curl "http://localhost:8000/deposit?number=1001&amount=100"

Deverá ver o seguinte resultado:

1001 - John - 100

Se fizer a mesma chamada várias vezes, o saldo da conta continuará a aumentar. Tente confirmar se o accounts mapa na memória está atualizado em tempo de execução. Se você parar o programa, todos os depósitos que você fez serão perdidos, mas isso é esperado nesta versão inicial.

Expor o método de retirada

Finalmente, vamos expor o método para retirar dinheiro de uma conta. Novamente, vamos primeiro criar a withdraw função no programa principal. A função validará as informações do número da conta, retirará e imprimirá qualquer erro que você receber do pacote principal. Adicione a seguinte função ao seu programa principal:

func withdraw(w http.ResponseWriter, req *http.Request) {
    numberqs := req.URL.Query().Get("number")
    amountqs := req.URL.Query().Get("amount")

    if numberqs == "" {
        fmt.Fprintf(w, "Account number is missing!")
        return
    }

    if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid account number!")
    } else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid amount number!")
    } else {
        account, ok := accounts[number]
        if !ok {
            fmt.Fprintf(w, "Account with number %v can't be found!", number)
        } else {
            err := account.Withdraw(amount)
            if err != nil {
                fmt.Fprintf(w, "%v", err)
            } else {
                fmt.Fprintf(w, account.Statement())
            }
        }
    }
}

Agora adicione o /withdraw main() ponto de extremidade na função para expor a lógica que você tem na withdraw() função. Modifique a main() função para ter esta aparência:

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }

    http.HandleFunc("/statement", statement)
    http.HandleFunc("/deposit", deposit)
    http.HandleFunc("/withdraw", withdraw)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

Se você não vir nenhum erro ou saída ao executar o programa (go run main.go), ele está funcionando corretamente. Abra um navegador da Web e digite a URL http://localhost:8000/withdraw?number=1001&amount=100ou execute o seguinte comando em outro shell enquanto o programa estiver em execução:

curl "http://localhost:8000/withdraw?number=1001&amount=100"

Deverá ver o seguinte resultado:

the amount to withdraw should be greater than the account's balance

Observe que o erro que estamos recebendo vem do pacote principal. Quando o programa é iniciado, o saldo da conta é zero. Portanto, você não pode retirar nenhuma quantia de dinheiro. Ligue para o /deposit ponto de extremidade algumas vezes para adicionar fundos e ligue para o /withdraw ponto de extremidade novamente para confirmar que está funcionando:

curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/withdraw?number=1001&amount=100"

Deverá ver o seguinte resultado:

1001 - John - 200

Está feito! Você criou uma API da Web para expor a funcionalidade de um pacote criado do zero. Vá para a próxima seção para continuar praticando. Desta vez, ser-lhe-á apresentado um desafio onde escreverá a sua própria solução.