11  Golem

Neste capítulo, vamos apresentar o framework Golem para a construção de aplicativos Shiny prontos para serem colocados em produção.

11.1 Motivação

O destino final de aplicativos Shiny costuma ser um ambiente de produção diferente do ambiente de desenvolvimento. Seja um servidor próprio, uma máquina na nuvem ou o shinyapps.io, o nosso app precisa funcionar nesses ambientes, não apenas na nossa máquina.

Uma vez no ambiente de produção, aplicativos Shiny costumam ficar lá por um bom tempo, gerando a necessidade de manutenção períodica e/ou atualizações. A depender de como o app foi desenvolvido, essas tarefas podem ficar muito mais trabalhosas. Seria interessante, nesse sentido, ter um framework de desenvolvimento que facilitasse a organização e documentação do código e o controle das dependências. É para isso que o Golem foi criado.

O Golem é um framework para desenvolvimento de aplicativos Shiny prontos para serem colocados em produção. As vantagens são:

  • padroniza a organização dos scripts e demais arquivos do seu app;

  • integra com pacotes que aceleram o desenvolvimento do código;

  • motiva a documentação do código;

  • e facilita o compartilhamento e a reutilização de códigos em outros projetos e com outras pessoas.

Na próxima seção, abordaremos como usar o pacote golem para obter essas vantagens.

11.2 Como usar?

Antes de mais nada, precisamos instalar o pacote.

install.packages("golem")

Para criar um app dentro do framework Golem, basta rodar o seguinte código:

golem::create_golem("~/Documents/meuapp")

Esse código vai criar uma pasta chamada meuapp/ dentro de ~/Documents/ (você pode especificar qualquer outra pasta no seu computador). Essa pasta vai conter diversos arquivos que lhe permitirão iniciar o desenvolvimento do seu app dentro do Golem.

Antes de falarmos dos arquivos dessa pasta, precisamos ter em mente que usar o pacote golem diz muito mais respeito a seguir uma filosofia do que a aprender uma ferramenta. Como os próprios autores descrevem

Golem is an opinionated framework for building production-grade shiny applications.

Isto é, para usar o Golem precisamos construir nosso app de um jeito específico, que os autores consideram ser o melhor. Com relação ao pacote em si, criada a estrutura inicial com a função golem::create_golem(), você poderia continuar o desenvolvimento do app dentro desse framework sem utilizar nenhuma outra função do golem1. O que realmente importa é seguir as seguintes premissas:

  • um aplicativo Golem é construído como um pacote R;

  • sempre que conveniente, devemos dividir o nosso app em módulos;

  • e devemos documentar as funções que compõem o aplicativo.

Assim, como já falamos de módulos no capítulo anterior, para entender melhor o Golem, precisamos falar um pouco de pacotes.

11.3 Pacotes

Se você nunca construiu um pacote de R, recomendamos fortemente a leitura do livro R Packages, em especial os 12 primeiros capítulos. Também recomendamos que treine construindo alguns pacotes simples fora do Shiny/Golem antes de continuar (veja os exercícios deste capítulo). Esse passo atrás é importante para que você saiba diferenciar o que é um pacote de R, o que é o Shiny e o que é o Golem.

Dito isso, para aqueles que por imprudência ou falta de tempo continuarão este texto sem uma base sobre o tema “pacotes”, vamos apresentar os pontos necessários para o entendimento do Golem.

11.3.1 O que é um pacote?

Um pacote de R é uma forma específica de organizar código, seguindo o protocolo descrito pela R Foundation.

Pacotes são a unidade fundamental de código R reprodutível.

— Wickham & Bryan

Um pacote inclue funções em R, documentação sobre como usá-las, testes e dados de exemplo.

De maneira geral, as funções de um pacote tentam resolver bem um problema em específico. O pacote dplyr, por exemplo, possui funções especializadas em manipular bases de dados, já o pacote ggplot2 possui funções para a construção de gráficos.

11.3.2 Estrutura básica do pacote

A seguir, apresentaremos a estrutura básica (arquivos e pastas) de qualquer pacote R.

  • DESCRIPTION: define o nome, descrição, versão, licença, dependências e outras caracaterísticas do pacote. É um arquivo de metadados.

  • LICENSE: especifica os termos de uso e distribuição do seu pacote.

  • .Rbuildignore: lista arquivos que não devem ser incluídos ao compilar o pacote R a partir do código-fonte, isto é, arquivos que são úteis apenas no desenvolvimento e não serão enviados para quem instalar o pacote.

  • NAMESPACE: este arquivo declara as funções que o pacote exporta (que ficam disponível quando alguém usa library()) e as funções que seu pacote importa de outros pacotes. Ele é criado automaticamente a partir da documentação das funções do pacote. Não devemos editar este arquivo manualmente.

  • R/: pasta onde fica o código R das funções do pacote. Essa pasta não deve conter subdiretórios.

11.3.3 Criando pacotes

Uma maneira fácil de criarmos a estrutura básica de um pacote é usamos a função usethis::create_package(). Você deve passar um caminho como ~/Documents/meupacote e uma nova pasta chamada meupacote será criada dentro da pasta Documents. Essa pasta será tanto um projeto do RStudio quanto um pacote, ambos chamados meupacote.

Não adicione acentos, caracteres especiais e espaços no nome do pacote, assim como nos arquivos que você criar dentro dele.

usethis::create_package("~/Documents/meupacote")

11.3.4 A pasta R/

Dentro de um pacote, a pasta R/ só pode ter scripts R com funções. Guardaremos nela todas as funções que farão parte do nosso pacote, mesmo que elas sejam apenas funções usadas internamente.

As funções colocadas dentro dessa pasta nunca devem ser rodadas diretamente. Se você quiser testá-las, deve fazer isso “carregando as funções”, isto é, usando a função devtools::load_all(). Isso fará com que todas as funções dentro da pasta R/ fiquem disponíveis na sua sessão, algo equivalente a fazer library(meupacote), mas com a diferença de também carregar as funções não exportadas.

Podemos usar usethis::use_r("nome-do-arquivo") para criar um arquivo script R dentro da pasta R/.

11.3.5 Dependências

Sem os inúmeros pacotes criados pela comunidade, o R provavelmente já estaria no porão da Ciência de Dados. Por isso, a primeira coisa que escrevemos nos nossos scripts quase sempre é library(algumPacoteLegal). Quando estamos construindo um pacote, é comum querermos utilizar dentro dele outros pacotes que não apenas o R base. Esses pacotes são chamados de dependências.

Ao desenvolver um pacote, a função library() nunca deve ser utilizada2, e todas as funções externas devem ter seus pacotes de origem explicitamente referenciados pelo operador ::. Embora seja chato especificar todos os pacotes, isso traz uma vantagem: as dependências do código estarão sempre atualizadas, pois elas estarão sempre atreladas às próprias funções sendo utilizadas.

Sempre que você utilizar um pacote dentro do pacote que está desenvolvendo, você deve especificá-lo como dependência no arquivo DESCRIPTION. Isso dirá ao R que, ao instalar o seu pacote, ele também precisa instalar todos os pacotes listados como dependência nesse arquivo. Você pode fazer isso facilmente utilizando usethis::use_package(). O código abaixo registra o pacote dplyr como dependência de um pacote sendo construído.

usethis::use_package("dplyr")

Se você está usando um pacote em desenvolvimento a partir de um repositório do GitHub, por exemplo, você pode usar a função usethis::use_dev_package() para adicioná-lo como dependência.

usethis::use_dev_package("dplyr", remote = "tidyverse/dplyr")

Leia a documentação dessas funções para mais informações sobre como adicionar dependências ao arquivo DESCRIPTION.

11.3.6 Dados

Se o seu pacote possuir bases de dados, como a dplyr::starwars, ou qualquer outro tipo de objeto do R, como pi ou letters, você deve colocá-los dentro de uma pasta chamada data/, na raiz do projeto, com a extensão .rda3. Isso pode ser feito facilmente a partir da função usethis::use_data().

Ao rodar o código abaixo, por exemplo, vamos criar uma pasta data/ na raiz do pacote, caso ela não exista ainda, e salvar nela o vetor base nomes no arquivo nomes.rda.

nomes <- c("Athos", "Bruna", "Caio")
usethis::use_data(nomes)

Fazendo isso, quando alguém carregar esse pacote, o objeto nomes ficará disponível para ser utilizado (igual a base starwars fica disponível quando carregamos o dplyr).

11.3.7 Documentação de funções

Para documentar as funções do seu pacote (i.e., gerar aquele documento mostrado quando rodamos ?mean, por exemplo), escrevemos comentários antes da definição da função nos scripts da pasta R/. Fazemos isso usando um tipo de comentários especial, o #', e marcadores que indicam qual parte da documentação estamos escrevendo. A estrutura dos comentários deve ser a seguinte:

#' Título da função
#'
#' Descrição da função
#'
#' @param a Descrição do primeiro parâmetro.
#' @param b Descrição do segundo parâmetro.
#'
#' @return Descrição do resultado (valor que sai da função).
#'
#' @export
fun <- function(a, b) {
  a + b
}

O marcador @export indica que a função ficará disponível quando rodarmos library(meupacote). Se você não quer que a função fique disponível, basta não colocar esse marcador.

Após escrever a documentação das suas funções dessa maneira, você deve rodar devtools::document() para que ela seja compilada e fique disponível no seu pacote (acessível pelo Help do R). Isso é feito por trás das cortinas pelo pacote roxygen2.

Dica: o RStudio disponibiliza um atalho para criar a estrutura da documentação de uma função. No menu superior, clique em Code -> Insert Roxygen Skeleton.

Para saber mais sobre documentação de pacotes, leia este capítulo do R Packages.

11.3.8 Instalando e compartilhando o seu pacote

Para verificar se você não feriu alguma regra de desenvolvimento de pacotes R, você pode usar a função devtools::check(). Essa função devolverá um relatório com possíveis problemas que o seu pacote pode ter, como erros de sintaxe, arquivos com extensões não permitidos, dependências não declaradas ou erros de documentação.

Para instalar o seu pacote localmente durante o desenvolvimento, rode a função devtools::install(). Isso é equivalente a ter o pacote instalado via install.packages().

O jeito mais fácil de disponibilizar o seu pacote na internet é subi-lo para um repositório público no Github. Dessa maneira, qualquer pessoa pode instalá-lo com a função remotes::install_github().

Para subir um pacote para o CRAN, o processo é (bem) mais burocrático. Se você quiser saber mais, leia este capítulo do R Packages.

11.4 Estrutura de um Golem app

Agora que já sabemos o básico sobre pacotes R, podemos voltar a falar do Golem.

Uma pasta criada pela função golem::create_golem() terá a seguinte estrutura:

#> ├── DESCRIPTION 
#> ├── NAMESPACE 
#> ├── R 
#> │   ├── app_config.R 
#> │   ├── app_server.R 
#> │   ├── app_ui.R 
#> │   └── run_app.R 
#> ├── dev 
#> │   ├── 01_start.R 
#> │   ├── 02_dev.R 
#> │   ├── 03_deploy.R 
#> │   └── run_dev.R 
#> ├── inst 
#> │   ├── app 
#> │   │   └── www 
#> │   │       └── favicon.ico 
#> │   └── golem-config.yml 
#> └── man 
#>     └── run_app.Rd

Veja que ela possui, entre outras coisas, a estrutura básica de um pacote. Vamos descrever cada arquivo mais detalhadamente e discutir a importância dele no contexto do desenvolvimento de um Shiny app:

  • O arquivo DESCRIPTION: guarda os metadados do pacote. No desenvolvimento de um aplicativo Shiny, ele vai guardar o nome do aplicativo, o que ele faz, as dependências dele, a versão (importante em projetos em produção que recebem atualizações periódicas) e quem contatar quando alguma coisa der errada. Com relação às dependências, isso quer dizer que, para rodar o seu app, o R precisará instalar todos os pacotes listados nesse arquivo.

  • O arquivo NAMESPACE: guarda metadados do pacote. Com esse arquivo, podemos carregar apenas funções específicas de um pacote dentro do nosso app4. O Golem faz isso com o pacote shiny nas funções app_ui() e app_server() para não precisarmos colocar shiny:: no início de cada função.

  • A pasta R/: guarda as funções do pacote. Como o app será feito dentro de um pacote R, todo o seu código será escrito em funções nessa pasta. O Golem já cria os arquivos para construirmos a UI e o servidor. Os scripts contendo os módulos do aplicativo também devem ser colocados nessa pasta, assim como scripts com funções úteis utilizadas em vários lugares do app.

  • O arquivo R/app_config.R: usado para especificar alguns mecanismos do Golem, como ler o arquivo de configuração localizado em inst/golem-config.yml.

  • O arquivo R/app_server.R: script com a função app_server(), onde você vai desenvolver o servidor do seu aplicativo.

  • O arquivo R/app_ui.R: script com a função app_ui(), onde você vai desenvolver a UI do seu aplicativo, e a função golem_add_external_resources(), utilizada para dizer ao Shiny que a pasta inst/app/www será utilizada como uma fonte de recursos externos, acessada pelo caminho www/nome_do_arquivo5. Além disso, o Golem inclui no HTML do seu app a conexão com todo arquivo CSS e JS que você coloca nessa pasta, então não precisamos fazer isso manualmente.

  • O arquivo R/run_app.R: script que contém a função run_app(), utilizada para rodar o app. Ela chama a função shiny::shinyApp(), que inicia o app localmente. A funcão shiny::shinyApp() está dentro da função golem::with_golem_options(), que recebe parâmetros passados para a run_app(). Esses parâmetros podem ser recuperados dentro do app com a função golem::get_golem_options(), deixando a parametrização de um aplicativo Shiny muito mais simples6.

  • dev/: pasta com scripts do golem que podem ser utilizados ao longo do desenvolvimento do app. Eles contêm uma lista de funções úteis que ajudam a configurar diversos aspectos do aplicativo. O uso desses scripts é opcional.

  • A pasta inst/app/www: local onde adicionaremos os recursos externos do aplicativo (imagens, arquivos CSS, fontes etc) que serão compartilhados com o navegador de quem estiver usando o app. A pasta inst é uma pasta especial no desenvolvimento de pacotes. Ela serve para adicionarmos arquivos que gostaríamos que fossem instalados com o pacote, como arquivos de teste, imagens etc. No contexto do Shiny, ela será utilizada para guardarmos arquivos auxiliares, como a própria pasta app/www, templates .Rmd de relatórios que o app gera, arquivos .md com textos que serão colocados no app, entre outros.

  • A pasta man/: contém a documentação do pacote, a ser gerada pelo roxygen2. É muito importante documentarmos todas as funções do nosso app, pois é muito comum que o código precise de ajustes ou atualizações no futuro. Uma breve descrição do que a função espera e o que ela devolve pode ser suficiente para ajudar a pessoa que for mexer no app no futuro (que pode ser você mesma) a esconomizar horas de debug.

11.5 Principais funções

Além da função golem::create_golem() que utilizamos para criar o nosso projeto com a estrutura do framework Golem, o pacote golem possui diversas funções úteis para usarmos durante o desenvolvimento do pacote. Vamos listar a seguir algumas delas:

  • golem::set_golem_name(): usada para mudar o nome do seu aplicativo. A mudança precisa ser feita tanto no arquivo DESCRIPTION quanto dentro da função app_sys() contida no arquivo R/app_config.R, e essa função realiza essas tarefas.

  • golem::add_module(): cria um arquivo na pasta R/ com o template de um módulo do Shiny. O nome do módulo (utilizado também como nome do arquivo) é passado pelo argumento name.

  • golem::add_css_file() e golem::add_js_file(): cria um arquivo vazio com extensão .css ou .js dentro da pasta inst/app/www do app. O nome desse arquivo pode ser passado pelo argumento name.

  • golem::use_utils_ui(): cria um arquivo chamado golem_utils_ui.R na pasta R/ com diversas funções úteis para serem utilizadas na UI de um Shiny app.

  • golem::use_utils_server(): cria um arquivo chamado golem_utils_server.R na pasta R/ com diversas funções úteis para serem utilizadas no servidor de um Shiny app.

  • golem::add_shinyappsio_file(): cria um arquivo app.R que pode ser utilizado para fazer o deploy do app para o shinyapps.io.

11.6 Deploy

Neste capítulo, vamos falar sobre como fazer o deploy do seu app feito em Golem em dois ambientes diferentes: no shinyapps.io e em qualquer máquina ou serviço que rode containers Docker.

11.6.1 Deploy para o shinyapps.io

Para subir um Shiny app para o shinyapps.io (veja a Seção 9.1 para mais detalhes), precisamos enviar o script primário do nosso app7, normalmente chamado de app.R, junto de todos os arquivos dos quais ele depende (outros scripts, bases de dados, imagens etc). A partir do RStudio, podemos fazer isso com alguns cliques a partir do botão Publish, disponível quando abrimos o arquivo app.R, ou a partir da função rsconnect::deployApp(), que consegue identificar esse script primário caso você não especifique para ela no argumento appPrimaryDoc.

No entanto, na estrutura do Golem que vimos até o momento não temos esse arquivo, já que todas as partes do app são construídas dentro de funções na pasta R/. O script run_app.R não pode ser considerado esse script primário pois a chamada da função shiny::shinyApp() está dentro da definição da função run_app(). O que fazer nesse caso?

A solução é criar um arquivo app.R dentro dessa estrutura. Podemos fazer isso usando a função golem::add_shinyappsio_file(). O arquivo será criado na pasta raiz do seu projeto e poderá ser utilizado como script primário do seu app, isto é, a partir dele você poderá usar o botão Publish. Além do app.R, você deverá subir para o shinyapps.io todos arquivos que constituem o seu app/pacote.

O arquivo app.R criado vai conter a chamada das seguintes funções:

pkgload::load_all()
options("golem.app.prod" = TRUE)
run_app()
  • pkgload::load_all() vai carregar o as funções definidas na pasta R/.

  • options("golem.app.prod" = TRUE) vai definir a variável de sistema golem.app.prod como TRUE. Isso é opcional e pode ser utilizado para criar versões diferentes de homologação ou produção para o seu app.

  • run_app(): roda o seu app, isto é, executa a função shiny::shinyApp().

Com esse arquivo criado, você também poderá utilizar normalmente a função rsconnect::deployApp() para fazer o deploy.

11.6.2 Deploy com Docker

O Docker é um software código aberto para o desenvolvimento e a implantação de aplicações embrulhadas em containers8. Ao colocar o seu app em um container, você garante que o ambiente onde ele ficará hospedado, seja ele qual for, terá todas as dependências que ele precisa para funcionar e estará devidamente configurado para que as pessoas possam acessá-lo por meio de uma URL.

Para dockerizar um app, precisamos criar um Dockerfile. Esse arquivo conterá todas as instruções necessárias para criar um container possuindo todas as dependências e configurações para hospedar o seu app (incluindo o Shiny Server). O pacote golem possui algumas funções para a criação desse arquivo:

  • golem::add_dockerfile(): adiciona um Dockerfile genérico, sem configurações para ambientes específicos.
  • golem::add_dockerfile_shinyproxy(): adiciona um Dockerfile com configurações específicas para o ShinyProxy, uma solução código aberto para o deploy de aplicativos Shiny.
  • golem::add_dockerfile_heroku(): adiciona um Dockerfile com configurações específicas para o Heroku, um serviço para hospedagem de qualquer tipo de aplicação dockerizada.

O Dockerfile será criado na pasta raiz do seu projeto. Para testá-lo localmente, caso você tenha o Docker instalado na sua máquina, basta rodar no Terminal:

docker build -t meuApp .
docker run -p 8080:80 meuApp

Se você precisar trocar a configuração padrão do Shiny Server, crie um novo shiny-server.conf na pasta inst/app e insira a seguinte linha no Dockerfile antes da linha EXPOSE 80:

COPY ./inst/app/shiny-server.conf /etc/shiny-server/shiny-server.conf

Além do ShinyProxy e do Heroku, você também pode subir o seu app dockerizado para plataformas como a AWS e o Google Cloud Engine.

11.7 Exercícios

  1. Quais são as vantagens de se usar o Golem?

  2. Qual a diferença entre usar Golem e usar apenas módulos?

  3. Podemos seguir o framework Golem sem usar o pacote golem. Verdadeiro ou falso?

  4. Em qual arquivo na estrutura de um pacote R definimos as suas dependências?

  5. Construa um pacote R que tenha as seguintes funções:

  • soma(x, y): realiza a soma x + y;

  • subtração(x, y): realiza a subtração x - y;

  • mult(x, y): realiza a multiplicação x * y;

  • divisao(x, y): realiza a divisão x / y.

  1. Construa um pacote R que tenha as seguintes funções:
  • read_csvs(arquivos, empilhar = FALSE): ela recebe um vetor com caminhos de arquivos .csv contendo bases de dados e devolve uma lista de tibbles com cada uma das bases se empilhar = FALSE e uma tibble com todas as bases empilhadas se empilhar = TRUE.

  • read_excels(arquivos, empilhar = FALSE): a mesma ideia, mas para arquivos .xlsx.

  1. Aplique o framework Golem no app disponibilizado neste link. Ele utiliza a base de dados Pokemon, que pode ser baixada clicando aqui. Além disso,
  1. Transforme o shinydashboard em Bs4Dash: construa ao menos a UI do zero.

  2. Modularize o app (cada página do dashboard deve ser um módulo diferente).

  3. Refaça os gráficos utilizando alguma biblioteca javascript (plotly, echarts, highcharts etc).

  4. Faça o deploy do app para o shinyapps.io.


  1. Na prática, poderíamos construir essa estrutura nós mesmos e usar o framework Golem sem usar o pacote golem.↩︎

  2. Imagine se, ao usarmos uma função de um pacote, ele carregasse um novo pacote na nossa sessão, possivelmente mascarando funções que estamos usando. Isso seria uma péssima prática.↩︎

  3. Arquivos .rda são extremamente estáveis, compactos e podem ser carregados rapidamente pelo R, tornando este formato o principal meio de guardar dados de um pacote.↩︎

  4. Isso é feito automaticamente pelo roxygen2 a partir da documentação das funções. Não edite esse arquivo na mão.↩︎

  5. Isto é, você não precisa incluir inst/app/ no caminho do arquivo. Por exemplo, se você quiser colocar a imagem inst/app/www/imagem.png no seu app, basta usar o caminho www/imagem.png.↩︎

  6. Podemos criar versões diferentes do app que serão executadas a depender dos parâmetros passados na função run_app().↩︎

  7. O arquivo com a chamada da função shiny::shinyApp() no final.↩︎

  8. Containers são uma unidade padrão de software que empacota um código e suas dependências de tal forma que a aplicação possa rodar de maneira confiável independentemente do ambiente computacional. Leia mais aqui.↩︎