Terraform vs. Nitric
Nitric is not designed to replace IaC tools like Terraform but instead introduces a method of bringing developer self-service for infrastructure directly into the developer application (Nitric's default deployment engines are built with Pulumi). Nitric can be augmented through use of tools like Pulumi or Terraform and even be fully customized using such tools see Custom Providers for more details.
Nitric is a framework designed to aid developers in building full cloud applications, including infrastructure. Terraform is an Infrastructure as Code (IaC) tool that enables explicit definition of infrastructure using HCL (or programming languages using CDKTF). The main differences between these are:
-
Nitric is cloud-agnostic, code that is written using Nitric constructs can be deployed to any cloud. Terraform supports many clouds, infrastructure declarations are explicitly defined for the provider that a resource is provided by.
-
Nitric defines not only the infrastructure but how it is interacted with at runtime, so infrastructure can be automatically inferred from application code to ensure best practice deployments and least privilege security.
-
Nitric provides tools for locally simulating a cloud environment (using the Nitric CLI), to allow an application to be tested locally. Terraform can be validated and linted as well as contract tested using terraform specific tooling.
Code Comparison
To showcase the power of the abstraction provided by Nitric here is a showcase of a Nitric program with an equivalent Terraform configuration with application code.
Nitric
import * as nitric from '@nitric/sdk'
const bucket = nitric.bucket('my-bucket').allow('read', 'write')
bucket.on('create', '*', async (ctx) => {
console.log(ctx.file.key, 'was created')
})
Terraform
const AWS = require('aws-sdk')
exports.handler = async (event) => {
const {
s3: { object },
} = event
console.log(object.key, 'was created')
}
Terraform
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
provider "aws" {
region = "us-west-2"
}
locals {
lambda_function_name = "upload_hello_txt_lambda"
}
resource "aws_s3_bucket" "this" {
bucket = "my-s3-bucket"
acl = "private"
}
data "archive_file" "lambda_zip" {
type = "zip"
source_file = "index.js"
output_path = "${path.module}/lambda.zip"
}
resource "aws_lambda_function" "this" {
function_name = local.lambda_function_name
role = aws_iam_role.lambda_role.arn
handler = "index.handler"
runtime = "nodejs14.x"
filename = data.archive_file.lambda_zip.output_path
timeout = 10
environment {
variables = {
BUCKET_NAME = aws_s3_bucket.this.bucket
}
}
}
resource "aws_lambda_permission" "allow_bucket" {
statement_id = "AllowExecutionFromS3Bucket"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.func.arn
principal = "s3.amazonaws.com"
source_arn = aws_s3_bucket.bucket.arn
}
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = aws_s3_bucket.bucket.id
lambda_function {
lambda_function_arn = aws_lambda_function.func.arn
events = ["s3:ObjectCreated:*"]
filter_prefix = "AWSLogs/"
filter_suffix = ".log"
}
depends_on = [aws_lambda_permission.allow_bucket]
}
resource "aws_iam_role" "lambda_role" {
name = "lambda_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "lambda_policy" {
name = "lambda_policy"
role = aws_iam_role.lambda_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "arn:aws:logs:*:*:*"
},
{
Action = [
"s3:PutObject"
]
Effect = "Allow"
Resource = "${aws_s3_bucket.this.arn}/*"
}
]
})
}
output "bucket_name" {
value = aws_s3_bucket.this.bucket
}
output "lambda_function_name" {
value = aws_lambda_function.this.function_name
}
In the Terraform example we get control over absolutely every facet of our infrastructure which is great, but for the majority of use cases, a repeatable pattern of deployment is enough and scales much better than applying fine-grained configuration every time we need to define a new deployment.
Differences
The table below contains the main differences you can see in the code examples, and also some that cannot fit in such a small app, but we'd like you to know about 🙂
Feature | Nitric | Terraform |
---|---|---|
Language | Your choice | HCL |
Lines of code | 7 | 123 |
Cloud Infrastructure | Inferred | Explicit |
Extensibility | Custom providers | Custom/dynamic providers |
Local Simulation | Built-in to CLI | N/A |
Cross-cloud support | Same code compiles to different clouds | No (need to write different config for different clouds) |
Provisioning engine | Custom providers can be written with any IaC tech (e.g. Terraform/AWS CDK) | Proprietary |
Testing | Same tests run on local simulator and cloud, without mocks | Need mocks for local testing |