<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aaron Jacobs</title>
    <description>The latest articles on DEV Community by Aaron Jacobs (@unconj1).</description>
    <link>https://dev.to/unconj1</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F288825%2F6c6c99e8-f686-42f1-82b9-ac4a78e36d39.jpg</url>
      <title>DEV Community: Aaron Jacobs</title>
      <link>https://dev.to/unconj1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/unconj1"/>
    <language>en</language>
    <item>
      <title>Annotating Deployments in Grafana Using the Process Start Time Metric</title>
      <dc:creator>Aaron Jacobs</dc:creator>
      <pubDate>Fri, 22 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/unconj1/annotating-deployments-in-grafana-using-the-process-start-time-metric-4415</link>
      <guid>https://dev.to/unconj1/annotating-deployments-in-grafana-using-the-process-start-time-metric-4415</guid>
      <description>&lt;p&gt;Grafana sports a feature called &lt;a href="https://grafana.com/docs/grafana/latest/reference/annotations/"&gt;Annotations&lt;/a&gt; that allow you to label a timestamp on a dashboard with meaningful events – most commonly deployments, campaigns, or outages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZMtBnbXJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/annotated-grafana-panel-with-start-time.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZMtBnbXJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/annotated-grafana-panel-with-start-time.png" alt="Process start time annotations on a Grafana panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(In this case annotating the simulated deployment of a &lt;a href="https://docs.fluentbit.io/manual/"&gt;FluentBit&lt;/a&gt; container, which I’ve used to forward container logs out of the cluster.)&lt;/p&gt;

&lt;p&gt;Annotations can be input manually, but the only recommendations I’ve seen to generate them automatically is to use something like &lt;a href="https://grafana.com/blog/2019/12/09/how-to-do-automatic-annotations-with-grafana-and-loki/"&gt;Loki&lt;/a&gt;, or &lt;a href="https://frederic-hemberger.de/notes/grafana/annotate-dashboards-with-deployments/"&gt;teaching your CI/CD system&lt;/a&gt; to interact with Grafana’s web API. However, if you’re running a simple Prometheus + Grafana stack (say, using the &lt;a href="https://coreos.com/blog/the-prometheus-operator.html"&gt;Prometheus Operator&lt;/a&gt; on Kubernetes), you might be reticent to add more complexity to your setup just to get deployment annotations.&lt;/p&gt;

&lt;p&gt;Fortunately, there’s a simpler alternative for this narrow case: you can use the &lt;code&gt;process_start_time_seconds&lt;/code&gt; metric from Prometheus to get an approximate idea of when apps or pods were started. I haven’t seen this approach recommended elsewhere, which is the purpose of this post.&lt;/p&gt;

&lt;p&gt;It turns out that &lt;code&gt;process_start_time_seconds&lt;/code&gt; is exposed by almost all applications because it’s one of the standard metrics &lt;a href="https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics"&gt;recommended by Prometheus itself&lt;/a&gt; and is exported by most client libraries automatically (including &lt;a href="https://unconj.ca/blog/introducing-openmetrics-for-r.html"&gt;my own&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;You can add annotations like the one in the image above as follows, assuming you have a &lt;code&gt;namespace&lt;/code&gt; and &lt;code&gt;pod&lt;/code&gt; &lt;a href="https://grafana.com/docs/grafana/latest/variables/templates-and-variables/"&gt;template variable&lt;/a&gt; defined:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B1CPUFdK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/create-start-time-annotation-in-grafana.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B1CPUFdK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/create-start-time-annotation-in-grafana.png" alt="Adding process start time annotations in the Grafana web UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s important to understand that these annotations will show only when new processes are started, most likely because of a deployment but also during rescheduling, scaling, or a pod failure – but since it is likely that you’d want to know about those as well, perhaps that’s a good thing.&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>prometheus</category>
      <category>metrics</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Structured Errors in Plumber APIs</title>
      <dc:creator>Aaron Jacobs</dc:creator>
      <pubDate>Sat, 07 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/unconj1/structured-errors-in-plumber-apis-1j4b</link>
      <guid>https://dev.to/unconj1/structured-errors-in-plumber-apis-1j4b</guid>
      <description>&lt;p&gt;If you’ve used the &lt;a href="https://www.rplumber.io/"&gt;Plumber package&lt;/a&gt; to make R models or other code accessible to others via an API, sooner or later you will need to decide how to handle and report errors.&lt;/p&gt;

&lt;p&gt;By default, Plumber will catch R-level errors (like calls to &lt;code&gt;stop()&lt;/code&gt;) and report them to users of your API as a JSON-encoded error message with HTTP status code 500 – also known as &lt;code&gt;Internal Server Error&lt;/code&gt;. This might look something like the following from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -v localhost:8000/
&amp;gt; GET /status HTTP/1.1
&amp;gt; Host: localhost:8000
&amp;gt; User-Agent: curl/7.64.0
&amp;gt; Accept: */*
&amp;gt; 
&amp;lt; HTTP/1.1 500 Internal Server Error
&amp;lt; Date: Sun, 24 Mar 2019 22:56:27 GMT
&amp;lt; Content-Type: application/json
&amp;lt; Date: Sun, 24 Mar 2019 10:56:27 PM GMT
&amp;lt; Connection: close
&amp;lt; Content-Length: 97
&amp;lt; 
* Closing connection 0
{"error":["500 - Internal server error"],"message":["Error: Missing required 'id' parameter.\n"]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There are two problems with this approach: first, it gives you almost zero control over how errors are reported to real users, and second, it’s badly behaved at the protocol level – HTTP status codes provide for much more granular and semantically meaningful error reporting.&lt;/p&gt;

&lt;p&gt;In my view, the key to overcoming these problems is treating errors as more than simply a message and adding additional context when they are emitted. This is sometimes called &lt;strong&gt;structured error handling&lt;/strong&gt; , and although it has not been used much historically in R, &lt;a href="https://www.dropbox.com/s/fi5fswytwatw1kk/condition-erum.pdf"&gt;this may be changing&lt;/a&gt;. As you’ll see, we can take advantage of R’s powerful condition system to implement rich error handling and reporting for Plumber APIs with relative ease.&lt;/p&gt;

&lt;p&gt;But first, it’s worth asking precisely what we want to get out of such an error handling system – that is, how can we distinguish errors we want our users to see?&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational vs. Programmer Errors
&lt;/h2&gt;

&lt;p&gt;Part of the issue here is that R (and Plumber) treat all errors as essentially the same, when in practice this is not the case.&lt;/p&gt;

&lt;p&gt;The folks at Joyent &lt;a href="https://www.joyent.com/node-js/production/design/errors#operational-errors-vs-programmer-errors"&gt;coined the terms&lt;/a&gt; &lt;strong&gt;operational&lt;/strong&gt; and &lt;strong&gt;programmer errors&lt;/strong&gt; in the context of Javascript, and I think this distinction is apt for R as well. To quote the article at length:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;People use the term “errors” to talk about both operational and programmer errors, but they’re really quite different. Operational errors are error conditions that all correct programs must deal with, and as long as they’re dealt with, they don’t necessarily indicate a bug or even a serious problem. “File not found” is an operational error, but it doesn’t necessarily mean anything’s wrong. It might just mean the program has to create the file it’s looking for first.&lt;/p&gt;

&lt;p&gt;By contrast, programmer errors are bugs. They’re cases where you made a mistake, maybe by forgetting to validate user input, mistyping a variable name, or something like that. By definition there’s no way to handle those. If there were, you would have just used the error handling code in place of the code that caused the error!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the context of Plumber APIs, we want to notify users of operational errors, because we require that they address these errors in order to use the API correctly. Programmer errors, on the other hand, might generate bizarre or misleading messages – so it’s not clear we want users to see them at all. At the same time, it is very important that &lt;em&gt;we&lt;/em&gt; see them so that we can start to track down the underlying bugs that caused them.&lt;/p&gt;

&lt;p&gt;Operational and programmer errors also have a very natural expression in terms of &lt;a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes"&gt;HTTP status codes&lt;/a&gt;; for the most part, &lt;code&gt;4xx&lt;/code&gt; codes are for client (operational) errors, and &lt;code&gt;5xx&lt;/code&gt;codes are for server (programmer) errors.&lt;/p&gt;

&lt;p&gt;By design, Plumber assumes that any error it encounters while running your code is a programmer error. This is the right default, but it does mean that you need to go out of your way to report operational errors instead. You can see this clearly by attempting to use R-style errors in a Plumber API.&lt;/p&gt;

&lt;h2&gt;
  
  
  R-Style Error Handling in Plumber
&lt;/h2&gt;

&lt;p&gt;Suppose we have the following simple &lt;code&gt;plumber.R&lt;/code&gt; file, which allows users to query the status of some familiar institutional patients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;records &amp;lt;- data.frame(
  id = 1:3,
  name = c("George", "Sally", "Michael"),
  admitted = c("2018-01-03", "2018-04-14", "2018-05-26"),
  released = c("2018-11-27", "2018-12-25", NA)
)

#* @param id:numeric* The patient's ID number.
#* @serializer unboxedJSON
#* @get /status
status &amp;lt;- function(id = NULL) {
  id &amp;lt;- as.integer(id)
  record &amp;lt;- records[records$id == id,]
  record$status &amp;lt;- if (!is.na(record$released)) "Released" else "Admitted"

  unclass(record)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can run this in the usual way with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server &amp;lt;- plumber::plumber("plumber.R")
server$run(port = 8000, debug = TRUE, swagger = FALSE)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And normal queries should look like the following from the command line (with&lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s localhost:8000/status?id=2 | jq
{
  "id": 2,
  "name": "Sally",
  "admitted": "2018-04-14",
  "released": "2018-12-25",
  "status": "Released"
}
$ curl -s localhost:8000/status?id=3 | jq
{
  "id": 3,
  "name": "Michael",
  "admitted": "2018-05-26",
  "released": null,
  "status": "Admitted"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Of course, there are a number of ways the endpoint could fail, so let’s add some R-style error handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status &amp;lt;- function(id) {
  if (missing(id)) {
    stop("Missing required 'id' parameter.", call. = FALSE)
  }
  id &amp;lt;- suppressWarnings(as.integer(id))
  if (is.na(id)) {
    stop("The 'id' parameter must be a positive integer.", call. = FALSE)
  }

  record &amp;lt;- records[records$id == id,]
  if (nrow(record) == 0) {
    stop("No patient found with id: ", id, ".", call. = FALSE)
  }
  record$status &amp;lt;- if (!is.na(record$released)) "Released" else "Admitted"

  unclass(record)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can then test some error conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:8000/status | jq
{
  "error": "500 - Internal server error",
  "message": "Error: Missing required 'id' parameter.\n"
}
$ curl localhost:8000/status?id=cats | jq
{
  "error": "500 - Internal server error",
  "message": "Error: The 'id' parameter must be a positive integer.\n"
}
$ curl localhost:8000/status?id=4 | jq
{
  "error": "500 - Internal server error",
  "message": "Error: No patient found with id: 4.\n"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You might notice that I passed &lt;code&gt;debug = TRUE&lt;/code&gt; to the &lt;code&gt;run()&lt;/code&gt; method above; this is because Plumber will only show the &lt;code&gt;message&lt;/code&gt; field in the error responses in “debug” mode. This is partly for privacy – error messages could expose internal state you’d prefer users not to see – but it also in recognition of the point I made above: random R error messages are rarely helpful to users.&lt;/p&gt;

&lt;p&gt;Plumber’s default error handler does a few useful things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Prints the error to the console, so we can see it on the server side. This is absolutely essential for tracking down bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sets the status code to &lt;code&gt;500&lt;/code&gt;; and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adds the error message to the response (as the &lt;code&gt;message&lt;/code&gt; field you see above),&lt;em&gt;but only when running in debug mode&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, this all means that we can’t use the default handler to send operational error messages back to the user. Instead, we can circumvent it by constructing error responses manually, or override it with smarter code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual Error Reporting
&lt;/h2&gt;

&lt;p&gt;To generate useful operational errors for users, we need to do two things: first, come up with a meaningful payload for errors; and second, ensure that errors set an appropriate HTTP status code. Both of these can be accomplished by manually modifying the response object that Plumber exposes as the magic parameter &lt;code&gt;res&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are many, many different takes on how to report errors in JSON; I’m going to use a pretty simple one here and include just a status code&lt;sup id="fnref1"&gt;1&lt;/sup&gt; and a message. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "status": 400,
  "message": "Missing required parameter."
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Similarly, there is some debate on how to map errors like “invalid parameter” to HTTP status codes, but here I’ll use &lt;code&gt;400&lt;/code&gt;. Both &lt;code&gt;422&lt;/code&gt; and &lt;code&gt;409&lt;/code&gt; are common alternatives. For the case when a patient can’t be found, I also think it make sense to use &lt;code&gt;404&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status &amp;lt;- function(id, res) {
  if (missing(id)) {
    res$status &amp;lt;- 400
    res$body &amp;lt;- jsonlite::toJSON(auto_unbox = TRUE, list(
      status = 400,
      message = "Missing required 'id' parameter."
    ))
    return(res)
  }
  id &amp;lt;- suppressWarnings(as.integer(id))
  if (is.na(id)) {
    res$status &amp;lt;- 400
    res$body &amp;lt;- jsonlite::toJSON(auto_unbox = TRUE, list(
      status = 400,
      message = "The 'id' parameter must be a positive integer."
    ))
    return(res)
  }

  record &amp;lt;- records[records$id == id,]
  if (nrow(record) == 0) {
    res$status &amp;lt;- 404
    res$body &amp;lt;- jsonlite::toJSON(auto_unbox = TRUE, list(
      status = 404,
      message = paste0("No patient found with id: ", id, ".")
    ))
    return(res)
  }
  record$status &amp;lt;- if (!is.na(record$released)) "Released" else "Admitted"

  unclass(record)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This gives us much nicer, more meaningful errors we can safely pass down to users of the API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s localhost:8000/status | jq
{
  "status": 400,
  "message": "Missing required 'id' parameter."
}
$ curl -s localhost:8000/status?id=moose | jq
{
  "status": 400,
  "message": "The 'id' parameter must be a positive integer."
}
$ curl -s localhost:8000/status?id=4 | jq
{
  "status": 404,
  "message": "No patient found with id: 4."
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The code to manipulate &lt;code&gt;res&lt;/code&gt; objects for error handling ends up involving a lot of copy &amp;amp; paste, especially for larger APIs where you want to report certain classes of errors in a standard way. Ideally, we want to provide some helper functions so that API authors do the right thing without needing to copy so much code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Emitting Errors via Custom Conditions
&lt;/h2&gt;

&lt;p&gt;The underlying machinery that powers R’s &lt;code&gt;stop()&lt;/code&gt;, &lt;code&gt;warning()&lt;/code&gt;, and &lt;code&gt;message()&lt;/code&gt;is the concept of a &lt;strong&gt;condition&lt;/strong&gt;. We can construct and “signal” error-like conditions using a simple S3 object that inherits from the &lt;code&gt;"error"&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api_error &amp;lt;- function(message, status) {
  err &amp;lt;- structure(
    list(message = message, status = status),
    class = c("api_error", "error", "condition")
  )
  signalCondition(err)
}

# Works like stop():
api_error("Bad request.", 400)
#&amp;gt; Error: Bad request.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Moreover, since these are S3 objects, we can use the &lt;code&gt;class&lt;/code&gt; attribute to sort out which errors are purposeful, operational errors that need to be reported to the user, and those that are not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error_handler &amp;lt;- function(req, res, err) {
  if (!inherits(err, "api_error")) {
    res$status &amp;lt;- 500
    res$body &amp;lt;- "{\"status\":500,\"message\":\"Internal server error.\"}"

    # Print the internal error so we can see it from the server side. A more
    # robust implementation would use proper logging.
    print(err)
  } else {
    # We know that the message is intended to be user-facing.
    res$status &amp;lt;- err$status
    res$body &amp;lt;- sprintf(
      "{\"status\":%d,\"message\":\"%s\"}", err$status, err$message
    )
  }
  res
}

# Add this to the server with
# server$setErrorHandler(error_handler)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I’d also advise writing some helper methods, like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;not_found &amp;lt;- function(message = "Not found.") {
  api_error(message = message, status = 404)
}

missing_params &amp;lt;- function(message = "Missing required parameters.") {
  api_error(message = message, status = 400)
}

invalid_params &amp;lt;- function(message = "Invalid parameter value(s).") {
  api_error(message = message, status = 400)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These helper functions allow us to simplify and clarify the code so that it is as concise and familiar looking as it was when we were using &lt;code&gt;stop()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status &amp;lt;- function(id, res) {
  if (missing(id)) {
    missing_params("Missing required 'id' parameter.")
  }
  id &amp;lt;- suppressWarnings(as.integer(id))
  if (is.na(id)) {
    invalid_params("The 'id' parameter must be a positive integer.")
  }

  record &amp;lt;- records[records$id == id,]
  if (nrow(record) == 0) {
    not_found(paste0("No patient found with id: ", id, "."))
  }
  record$status &amp;lt;- if (!is.na(record$released)) "Released" else "Admitted"

  unclass(record)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using a custom error handler and the structured error support of S3 conditions, we now have a way to emit operational errors with ease and a consistent JSON error reporting format. This is an essential piece of providing a robust, user-friendly Plumber API.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I like having the original status code as part of the error payload. That way, even if I don’t have access to the full original request (e.g. someone just copy &amp;amp; pasted the error message to me, or it’s not in the logs, or a proxy along the way did not forward it appropriately), I still have a good idea where to look. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>r</category>
      <category>plumber</category>
    </item>
    <item>
      <title>Writing Proprietary R Packages</title>
      <dc:creator>Aaron Jacobs</dc:creator>
      <pubDate>Tue, 26 Nov 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/unconj1/writing-proprietary-r-packages-5ein</link>
      <guid>https://dev.to/unconj1/writing-proprietary-r-packages-5ein</guid>
      <description>&lt;p&gt;&lt;em&gt;Author’s note: this is a lightly modified version of the talk I gave at the GTA R User’s Group in May of this year. You can find the original slides &lt;a href="https://github.com/gta-r-user-group/gtar/tree/master/2019-06-04/proprietary-r-packages"&gt;here&lt;/a&gt;. Unfortunately, the talk was not recorded.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As I have &lt;a href="https://unconj.ca/blog/copyright-in-closed-source-r-packages-the-right-way.html"&gt;noted before&lt;/a&gt;, most resources for R package authors are pitched at those writing open-source packages — usually hosted on &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt;, and with the goal of ending up on &lt;a href="https://r-project.org"&gt;CRAN&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These are valuable resources, and reflect the healthy free and open-source (FOSS) R package ecosystem. But it is not the whole story. Many R users, especially those working as data scientists in industry, can and should be writing packages for internal use within their company or organisation.&lt;/p&gt;

&lt;p&gt;Yet there is comparatively little out there about how to actually put together high-quality packages in these environments.&lt;/p&gt;

&lt;p&gt;This post is my attempt to address that gap.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.pinnacle.com"&gt;work&lt;/a&gt; we have more than 50 internal R packages, and I have been heavily involved in building up the culture and tooling we use to make managing those packages possible over the last two years.&lt;/p&gt;

&lt;p&gt;I’ll focus on three major themes: code, tooling, and culture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Should You Write R Packages for Internal Use?
&lt;/h2&gt;

&lt;p&gt;At the outset, it is worth repeating that R packages are the best way to share R code and keep it well-maintained and reliable. This matters even more inside an organisation or when you are part of a team.&lt;/p&gt;

&lt;p&gt;The most salient reason why this is the case is that common tools to make your R code robust, portable, and well-documented are &lt;a href="https://unconj.ca/blog/why-there-is-no-importfrom-in-r.html"&gt;only available&lt;/a&gt; for use with packages: &lt;code&gt;R CMD check&lt;/code&gt;, &lt;code&gt;testthat&lt;/code&gt;, and &lt;code&gt;roxygen&lt;/code&gt; are all good examples.&lt;/p&gt;

&lt;p&gt;I would push this further than most, and suggest you put as much R code as you can get away with inside packages. For instance, we put all production models in R packages, much of our ETL, and a good portion of our Shiny apps – which has recently become a lot easier due to the excellent &lt;a href="https://thinkr-open.github.io/golem/"&gt;&lt;strong&gt;golem&lt;/strong&gt; package&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code: What Can Internal Packages Contain?
&lt;/h2&gt;

&lt;p&gt;It is a oft-repeated observation that when you find yourself copy/pasting the same function or snippet of R code between projects, it might be time to push that code into a package.&lt;/p&gt;

&lt;p&gt;Inside a business or organisation, functions that I’ve seen generally fall into a small number of categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tools for making data easy to access and use correctly. For example, accessing the right database with the right credentials.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Plot themes and other internal conventions ported to R, such as presentation templates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Business logic, which are conversions and routines that are highly specific to your organisation or industry; and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Encoding process in code – e.g. automation of team or company-specific tasks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an illustration, all of the following are real functions from our internal packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Accessing data.
pull_data(...)
mongo_collection(...)

# Plot themes and templates.
theme_pinnacle(...)
pinnacle_presentation(...)

# Business logic.
vig_cents_to_percent(...)
get_clp_est(..)

# Encoding process.
send_to_slack(...)
rnd_release(...)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;There could be other good candidates. As I mentioned, we put models, Shiny applications, and ETL into packages when possible, some of which turns into reusable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tooling: Limitations and Opportunities
&lt;/h2&gt;

&lt;p&gt;Unlike the CRAN/FOSS world, in an organisation you’ll have limited power to choose the tools your organisation (already) uses for collaboration and development. And, unless you’ve got a compelling reason, you &lt;em&gt;should&lt;/em&gt; adopt your organisation’s existing tools.&lt;/p&gt;

&lt;p&gt;For example, my organisation uses TeamCity for continuous integration, which does not support R out of the box. In order to get access to shared CI resources, we had to make this possible (which involved using Docker and some custom scripts).&lt;/p&gt;

&lt;p&gt;The upside of this is that we could then hook into integrations used by the rest of the organisation. For instance, Slack alerts for R packages that pass or fail their CI tests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8-qsToqI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/teamcity.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8-qsToqI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/teamcity.png" alt="TeamCity Build Message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because everyone is required to use the same tools, you can sometimes turn this to your advantage and leverage a shared tool for opportunities you might not otherwise have.&lt;/p&gt;

&lt;p&gt;For example, since everyone is required to be in the same Slack channel or read their corporate email, you can ensure that everyone is notified of important R package releases – ie. this is our Slack bot that posts R package releases:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OSvnGqSW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/pkgtools.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OSvnGqSW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://unconj.ca/blog/images/pkgtools.png" alt="Slack announcements"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This kind of broadcast is genuinely impossible for CRAN packages, because there is simply no medium by which to contact all R users. FOSS communities are and always will be more decentralised and heterogeneous.&lt;/p&gt;

&lt;p&gt;Moreover, tools can create data (e.g. releases, downloads, commit activity, email messages, Slack messages) that can be analysed to help you measure and understand bottlenecks or problems with your internal processes, since you know they represent the whole picture. I’ve used these data to decide what would help my team by more productive on several occasions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Culture: Authorship vs. Maintainership
&lt;/h2&gt;

&lt;p&gt;Ultimately, I think the biggest difference between FOSS and proprietary R packages is a cultural one.&lt;/p&gt;

&lt;p&gt;Most CRAN packages are written by someone trying to scratch an itch. Maybe that’s a new statistical method or data transformation, or a new approach to a older ones; maybe it’s a new data format that you need access to from R; or maybe it’s just a bundle of cool stuff you’ve done that you want to make more widely accessible.&lt;/p&gt;

&lt;p&gt;There are some consequences of this model. Even if the package is released to CRAN, the expectation is that the original author will (1) design the APIs; (2) write the code; and (3) maintain the package. The author will make all the major decisions about where the package goes and be the authority on why code works the way it does.&lt;/p&gt;

&lt;p&gt;The author unconsciously acts as though they will maintain the code forever (or more likely: until the package is abandoned).&lt;/p&gt;

&lt;p&gt;I call this an &lt;strong&gt;Authorship paradigm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These packages have a &lt;a href="https://en.wikipedia.org/wiki/Bus_factor"&gt;bus factor&lt;/a&gt; of exactly 1 – that is, if the author gets hit by a bus, that’s probably the end of the project. It is not an usual state of affairs for FOSS software, by any means.&lt;/p&gt;

&lt;p&gt;Things are dramatically different for proprietary code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You likely inherited a codebase you did not design, and it is your &lt;strong&gt;job&lt;/strong&gt; to maintain it, to understand it, and to be the person to ask about bugs and new features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You will not maintain new code you have designed/written forever. At some point it will likely be someone else’s job.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I call this a &lt;strong&gt;Maintainership paradigm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What does this imply about writing R packages? Well, you should write the kind of R code you’d like to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Kind of R Package Would You Like to Maintain?
&lt;/h2&gt;

&lt;p&gt;This is not a trick question. You want to see&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clear R source code with helpful comments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Good documentation, with clear explanations and examples, and an overview of the main package features in a &lt;code&gt;README&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A test suite, both as additional examples and also to help prevent you from introducing regressions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A documented history: an up-to-date &lt;code&gt;NEWS&lt;/code&gt; file and clear git commit history.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, pretty much all of the usual advice. The difference is that you are primarily motivated by collaboration with current and future coworkers.&lt;/p&gt;

&lt;p&gt;In sum, I think that there is quite a lot of overlap between “best practice” package development in FOSS and proprietary environments. The main differences arise from the motivations of package authors, the tools and ecosystems they might operate in, and the nature of the code they might write.&lt;/p&gt;

</description>
      <category>r</category>
      <category>rstats</category>
    </item>
  </channel>
</rss>
