Chapter 2 Environment Setup & Shared Infrastructure
Before diving into specific projects, we need to establish the shared infrastructure that every production system requires: database connections, logging, configuration management, and utility functions. This chapter builds the foundation.
2.1 Package Management with renv
Production systems demand reproducibility. The renv package creates isolated, reproducible environments for R projects.
# Initialize renv in your project
renv::init()
# Install packages — they are recorded in renv.lock
install.packages("tidymodels")
# Snapshot the environment
renv::snapshot()
# On a new machine, restore the exact environment
renv::restore()Tip: Commit renv.lock to version control, but add renv/library/ to .gitignore.
2.2 Database Connection Management
2.2.1 Secure Credential Handling
Never hardcode credentials. Use environment variables or secret management systems.
2.2.2 Connection Pooling
For applications serving multiple concurrent users (Shiny apps, Plumber APIs), connection pooling prevents exhausting database resources.
library(pool)
db_pool <- dbPool(
drv = RPostgres::Postgres(),
dbname = Sys.getenv("DB_NAME"),
host = Sys.getenv("DB_HOST"),
port = as.integer(Sys.getenv("DB_PORT")),
user = Sys.getenv("DB_USER"),
password = Sys.getenv("DB_PASS"),
minSize = 2, # Minimum connections
maxSize = 10, # Maximum connections
idleTimeout = 300 # Close idle connections after 5 minutes
)
# Use pool exactly like a regular connection
pool::poolReturn(db_pool)2.2.3 Lazy Evaluation with dbplyr
The dbplyr package translates dplyr verbs into SQL, allowing you to work with database tables as if they were in-memory data frames.
library(dbplyr)
# This does NOT load data into R — it builds a SQL query
enterprise_customers <- tbl(con, "customers") %>%
filter(segment == "enterprise", mrr > 10000) %>%
select(customer_id, company_name, mrr, created_at)
# View the generated SQL
enterprise_customers %>% show_query()
# Only collect() brings data into R
results <- enterprise_customers %>% collect()Important: Always filter and aggregate before collect(). Bringing unfiltered data into R is the most common cause of memory issues in production.
2.3 Logging Infrastructure
Structured logging is essential for debugging production issues.
library(logger)
# Configure logging
log_threshold(INFO)
log_layout(layout_glue_colors)
# Usage throughout your code
log_info("Starting feature engineering pipeline")
log_warn("Missing values detected in {n_missing} rows")
log_error("Model training failed: {e$message}")
# For production, log to files or external systems
log_appender(appender_file("/var/log/r-app.log"))2.4 Safe Execution Patterns
Production code must handle failures gracefully.
safe_execute <- function(expr, fallback = NULL, context = "operation") {
tryCatch(
expr,
error = function(e) {
log_error("[{context}] Failed: {e$message}")
fallback
},
warning = function(w) {
log_warn("[{context}] Warning: {w$message}")
invokeRestart("muffleWarning")
}
)
}
# Usage
model <- safe_execute(
xgboost::xgb.train(params, dtrain, nrounds = 100),
fallback = NULL,
context = "model_training"
)2.6 Configuration Management
Centralize configuration to avoid magic numbers scattered through code.
2.7 Shared Utility Functions
Create a utils.R file with functions used across projects.
# R/utils.R
#' Format currency values
format_currency <- function(x, currency = "$") {
scales::dollar(x, prefix = currency)
}
#' Calculate date ranges for reporting
get_reporting_period <- function(period = "last_30_days") {
end_date <- Sys.Date()
start_date <- switch(period,
"last_7_days" = end_date - 7,
"last_30_days" = end_date - 30,
"last_quarter" = end_date - 90,
"last_year" = end_date - 365,
end_date - 30
)
list(start = start_date, end = end_date)
}
#' Save model with metadata
save_model_artifact <- function(model, path, metadata = list()) {
artifact <- list(
model = model,
metadata = c(
metadata,
list(
saved_at = Sys.time(),
r_version = R.version.string,
session_info = utils::sessionInfo()
)
)
)
readr::write_rds(artifact, path, compress = "xz")
}2.8 Testing with testthat
Production code requires automated tests.
library(testthat)
# tests/testthat/test-utils.R
test_that("format_currency handles basic cases", {
expect_equal(format_currency(1000), "$1,000")
expect_equal(format_currency(0), "$0")
expect_true(is.na(format_currency(NA)))
})
test_that("get_reporting_period returns valid dates", {
period <- get_reporting_period("last_30_days")
expect_equal(period$end - period$start, 30)
})2.9 Exercises
- Set up
renvfor a new project and install all packages from this chapter. - Write a connection pool wrapper that automatically retries failed connections up to 3 times with exponential backoff.
- Create a logging configuration that writes INFO and above to the console, but only WARN and above to a file.
- Write unit tests for the
safe_execute()function, verifying it returns the fallback on error and logs appropriately.
With our infrastructure in place, we’re ready to build our first production project: a customer churn prediction system with automated retention workflows.