[{"data":1,"prerenderedAt":522},["ShallowReactive",2],{"\u002Fen\u002Fpost\u002F2021\u002F06\u002Fterraform-atomic-design":3},{"id":4,"title":5,"author":6,"body":7,"createdAt":508,"description":509,"extension":510,"meta":511,"navigation":512,"path":513,"seo":514,"slug":515,"stem":516,"tags":517,"__hash__":521},"posts\u002Fposts\u002F2021\u002F06\u002Fterraform-atomic-design.md","Terraform: Atomic Design",null,{"type":8,"value":9,"toc":495},"minimark",[10,15,27,37,40,43,48,62,65,74,77,80,93,96,99,103,106,124,127,132,144,153,156,166,173,206,208,214,217,221,224,227,232,235,238,244,254,266,270,276,285,288,299,306,310,324,331,337,351,354,365,376,380,389,397,408,417,425,432,435,441,445,448,456,459,467,471],[11,12,14],"h2",{"id":13},"intro","Intro",[16,17,18,19,26],"p",{},"Following ",[20,21,25],"a",{"href":22,"rel":23},"https:\u002F\u002Fpragprog.com\u002Ftitles\u002Ftpp20\u002Fthe-pragmatic-programmer-20th-anniversary-edition\u002F",[24],"nofollow","The Pragmatic Programmer"," mantra, I do my best to ...",[28,29,30],"blockquote",{},[16,31,32,36],{},[33,34,35],"strong",{},"Learn at least one new language every year."," Different languages solve the same\nproblems in different ways. By learning several different approaches, you can\nhelp broaden your thinking and avoid getting stuck in a rut.",[16,38,39],{},"Not necessarily to show it off or to be capable of talking about random\ntechnologies, but to expand and train my problem-solving skills, to get new\nperspectives when approaching a challenge.",[16,41,42],{},"We might not notice it but when we learn (or have learned) to code we aren't\njust learning to type some characters that a compiler\u002Finterpreter can\nunderstand, it is a new way of thinking, a new way of breaking down solutions\n(into sequential steps).",[28,44,45],{},[16,46,47],{},"It doesn't matter whether you ever use any of these technologies on a project,\nor even whether you put them on your resume. The process of learning will expand\nyour thinking, opening you to new possibilities and new ways of doing things.\nThe cross-pollination of ideas is important;",[16,49,50,51,55,56,61],{},"As someone who works intensively with infrastructure components (servers,\ndatabases, Kubernetes, CI\u002FCD, etc) I aimed for something completely different\nthis year. Something that stands on ",[52,53,54],"em",{},"a whole different spectrum"," of the system,\nthis year I decided to learn ",[20,57,60],{"href":58,"rel":59},"https:\u002F\u002Fflutter.dev\u002F",[24],"Flutter",".",[16,63,64],{},"In-a-nutshell, Flutter is a better React Native. A framework that enables\nimplementation of GUI applications for multiple platforms with a single code\nbase.",[16,66,67,68,73],{},"Then it reminded me a discussion I had with a friend in the past about React\ncomponents and the ",[20,69,72],{"href":70,"rel":71},"https:\u002F\u002Fbradfrost.com\u002Fblog\u002Fpost\u002Fatomic-web-design\u002F",[24],"Atomic Design"," methodology, which helps to structure web\ncomponents into modules.",[16,75,76],{},"In the Atomic Design methodology, the granularity of modules is distinguished by\nusing chemistry inspired names: atoms, molecules and organisms.",[16,78,79],{},"Then the connection of the ideas from",[81,82,83,87,90],"ul",{},[84,85,86],"li",{},"Pragmatic Programmer's cross-pollination to",[84,88,89],{},"Atomic Design (on Flutter components) to",[84,91,92],{},"Terraform modules",[16,94,95],{},"came almost like a thunderbolt, striking me with this insight when I was working\nwith a huge legacy Terraform code base refactoring with lots of code duplication\n(read: copy+paste, \"we fix it later\", then the author quits the company and\nnever fix anything).",[16,97,98],{},"Although initially proposed as a Web UI methodology, Infrastructure as Code\ntools such as Terraform that makes heavy usage of modules can benefit from\nAtomic Design to improve its code reusability and massively reduce duplication.",[11,100,102],{"id":101},"details","Details",[16,104,105],{},"The Atomic Design methodology proposes five distinct levels, listed from the\nfinest to the thickest granularity:",[107,108,109,112,115,118,121],"ol",{},[84,110,111],{},"Atom;",[84,113,114],{},"Molecules;",[84,116,117],{},"Organisms;",[84,119,120],{},"Templates;",[84,122,123],{},"Pages.",[16,125,126],{},"However, to extract the gist, we'll only be focusing on Atoms, Molecules, and\nOrganisms (from 1. to 3.). Templates and Pages are too specialized for Web UI\ndevelopment.",[128,129,131],"h3",{"id":130},"atoms","Atoms",[16,133,134,135,139,140,143],{},"Atoms represent the finest grain in terms of granularity in the design. When\nreferring specifically to its implementation in Terraform a ",[136,137,138],"code",{},"resource"," and a\nsmall scoped single-purpose ",[136,141,142],{},"module"," could be used interchangeably.",[16,145,146,147,149,150,152],{},"Sometimes the idea of turning a simple resource into a module makes sense to\nease parameterization and reusability, especially when it is necessary to parse\ninputs. Although, due to its extreme limited scope it might not look attractive\nto convert the ",[136,148,138],{}," into a ",[136,151,142],{}," at first sight, on the long run it\npays off to do so in order to achieve scalability and reproducibility.",[16,154,155],{},"e.g.:",[157,158,164],"pre",{"className":159,"code":161,"language":162,"meta":163},[160],"language-hcl","data \"aws_route53_zone\" \"default\" {\n  zone_id = var.zone_id\n  name    = var.zone_name\n}\n\nresource \"aws_route53_record\" \"default\" {\n  zone_id = data.aws_route53_zone.default.zone_id\n  name    = var.name\n\n  ttl  = var.ttl\n  type = var.record_type\n\n  records = var.records\n\n  dynamic \"alias\" {\n    for_each = [var.alias]\n\n    content {\n      name = each.value.name\n      zone_id = try(each.value.zone_id, data.aws_route53_zone.default.zone_id)\n\n      evaluate_target_health = lookup(\n        each.value,\n        \"evaluate_target_health\",\n        false,\n      )\n    }\n  }\n}\n","hcl","",[136,165,161],{"__ignoreMap":163},[16,167,168,169,172],{},"In this case, even though ",[136,170,171],{},"aws_route53_record"," is a simple resource that might\nfeel too narrow in scope to write a module, the implementation of the module\nallows to bundle the AWS Route53 Zone data source together, which helps to:",[107,174,175,182,195],{},[84,176,177,178,181],{},"provide a simpler contract by allowing the usage of ",[136,179,180],{},"zone_name"," alone;",[84,183,184,185,187,188,190,191,194],{},"validate the ",[136,186,180],{}," input, ensuring that a given ",[136,189,180],{}," corresponds to an\nactual ",[33,192,193],{},"existing and valid"," AWS resource;",[84,196,197,198,201,202,205],{},"same goes to ",[136,199,200],{},"zone_id",", which will feel (and oftentimes, be) redundant,\n",[52,203,204],{},"when"," specified as an input Terraform will read the data from AWS API\nensuring consistency.",[16,207,155],{},[157,209,212],{"className":210,"code":211,"language":162,"meta":163},[160],"module \"awesome_dns_fqdn\" {\n  source = \"path\u002Fto\u002Fmodules\u002Fatoms\u002Faws_route53_record\"\n  version = \"~> 1.0\"\n\n  name      = \"record.example.com\"\n  zone_name = \"example.com.\"\n\n  record_type = \"CNAME\"\n  records     = [\"1.2.3.4\"]\n}\n",[136,213,211],{"__ignoreMap":163},[16,215,216],{},"Hence, resources and modules are sometimes interchangeable as they deliver the\nsame outcome for the finest resources' granularity.",[128,218,220],{"id":219},"molecules","Molecules",[16,222,223],{},"When groups of atoms are bounded together, they create a molecule which is the\nsmallest fundamental unit of a compound.",[16,225,226],{},"Contrary to the original Atomic Design for Web UI, in Terraform, Atoms are\nuseful on their own. However, the usage of atoms comes with a high price on\nscalability: code duplication. Actually, duplication is an understatement, it is\nmore like code exponentiation (more on this later).",[228,229,231],"h4",{"id":230},"implementation-example","Implementation example",[16,233,234],{},"Suppose we are creating a public facing API Gateway that needs a DNS record.",[16,236,237],{},"Let's compose it with the previous example:",[157,239,242],{"className":240,"code":241,"language":162,"meta":163},[160],"data \"aws_route53_zone\" \"default\" {\n  name = var.zone_name\n}\n\nmodule \"awesome_api_gateway_certificate\" {\n  source  = \"terraform-aws-modules\u002Facm\u002Faws\"\n  version = \"~> v3.0\"\n\n  domain_name = var.domain_name\n  zone_id     = data.aws_route53_zone.default.zone_id\n\n  wait_for_validation = true\n}\n\nmodule \"awesome_api_gateway\" {\n  source = \"terraform-aws-modules\u002Fapigateway-v2\u002Faws\"\n  version = \"~> 1.0\"\n\n  name          = var.api_gateway_name\n  description   = var.api_gateway_description\n  protocol_type = \"HTTP\"\n\n  cors_configuration = {\n    allow_headers = [\n      \"content-type\",\n      \"x-amz-date\",\n      \"authorization\",\n      \"x-api-key\",\n      \"x-amz-security-token\",\n      \"x-amz-user-agent\",\n    ]\n    allow_methods = [\"*\"]\n    allow_origins = [\"*\"]\n  }\n\n  # Custom domain\n  domain_name                 = var.domain_name\n  domain_name_certificate_arn = module.awesome_api_gateway_certificate.acm_certificate_arn\n\n  # Routes and integrations\n  integrations = var.api_gateway_integrations\n}\n\nmodule \"awesome_dns_fqdn\" {\n  source  = \"path\u002Fto\u002Fmodules\u002Fatoms\u002Faws_route53_record\"\n  version = \"~> 1.0\"\n\n  name    = var.domain_name\n  zone_id = data.aws_route53_zone.default.zone_id\n\n  record_type = \"CNAME\"\n  alias     = {\n    name    = module.awesome_api_gateway.apigatewayv2_domain_name_configuration[0].target_domain_name\n    zone_id = module.awesome_api_gateway.apigatewayv2_domain_name_configuration[0].hosted_zone_id\n  }\n}\n",[136,243,241],{"__ignoreMap":163},[16,245,246,247,249,250,253],{},"This helps illustrating an example in which the ",[136,248,171],{}," atom could\nbe easily replaced with its equivalent resource and it would still provide the\n",[33,251,252],{},"same"," outcome.",[16,255,256,257,259,260,262,263,265],{},"Commonly it is possible to use ",[136,258,142],{}," and ",[136,261,138],{}," interchangeably as Atoms,\nthe decision of whether or not to implement a ",[136,264,142],{}," is ultimately defined by\nthe need of parsing and\u002For validating the inputs (variables).",[228,267,269],{"id":268},"usage-example","Usage example",[157,271,274],{"className":272,"code":273,"language":162,"meta":163},[160],"module \"awesome_lambda\" {\n  source  = \"path\u002Fto\u002Fmodules\u002Fmolecules\u002Faws_lambda_function\"\n  version = \"~> 1.0\"\n\n  function_name = \"awesome\"\n  description   = \"An Awesome lambda function for the Awesome API Gateway\"\n  handler       = \"index.lambda_handler\"\n  runtime       = \"python3.8\"\n\n  # Incomplete implementation, don't use this on production\n}\n\nmodule \"another_awesome_lambda\" {\n  source  = \"path\u002Fto\u002Fmodules\u002Fmolecules\u002Faws_lambda_function\"\n  version = \"~> 1.0\"\n\n  function_name = \"awesome\"\n  description   = \"An Awesome lambda function for the Awesome API Gateway\"\n  handler       = \"index.lambda_handler\"\n  runtime       = \"python3.8\"\n\n  # Incomplete implementation, don't use this on production\n}\n\nmodule \"awesome_api_gateway\" {\n  source  = \"path\u002Fto\u002Fmodules\u002Fmolecules\u002Faws_api_gateway\"\n  version = \"~> 1.0\"\n\n  domain_name = \"record.example.com\"\n  zone_name   = \"example.com.\"\n\n  api_gateway_name        = \"awesome-api-gateway\"\n  api_gateway_description = \"An Awesome API Gateway\"\n\n  api_gateway_integrations = {\n    \"POST \u002F\" = {\n      lambda_arn             = module.awesome_lambda.function_arn\n      payload_format_version = \"2.0\"\n    }\n\n    \"$default\" = {\n      lambda_arn = module.another_awesome_lambda.function_arn\n    }\n  }\n}\n",[136,275,273],{"__ignoreMap":163},[16,277,278,279,284],{},"As you probably have already realized, when the level of abstraction goes up\n(e.g. from atom to molecule) the module implementation is in itself a good\nimplementation example (i.e. as in ",[20,280,283],{"href":281,"rel":282},"https:\u002F\u002Fgithub.com\u002Fterraform-aws-modules\u002Fterraform-aws-lambda\u002Fblob\u002Fmaster\u002Fmain.tf",[24],"community modules examples",").",[16,286,287],{},"They help to self-document the usage and implementation of a given module and\nthrough generic implementations it allows us to have multiple molecules\nimplementing multiple distinct use-cases. e.g.:",[107,289,290,293,296],{},[84,291,292],{},"Public API Gateway with DNS record + TLS certificate;",[84,294,295],{},"Public API Gateway v1, no DNS record;",[84,297,298],{},"Private API Gateway.",[16,300,301,302,305],{},"Why would we chose to implement multiple times the Atom modules in order to\ncreate multiple distinct use-cases? We are getting closer to the ",[52,303,304],{},"code\nexponentiation"," problem and solution proposal. Can you feel it?",[128,307,309],{"id":308},"organisms","Organisms",[16,311,312,313,317,318,323],{},"Going further, the ",[20,314,316],{"href":315},"#usage-example","example of composition for molecules"," can have its hard-coded\nvalues turned into variables in order to compose an Organism, which can\nfacilitate the implementation of the same definition across different\nenvironments. Thus, achieving reproducibility as well as the ",[20,319,322],{"href":320,"rel":321},"https:\u002F\u002F12factor.net\u002Fdev-prod-parity",[24],"Factor X."," of the\nTwelve Factor App.",[16,325,326,327,330],{},"However, it is important to note that the level of abstraction between Organisms\nand Molecules can be easily confused or misunderstood. Generally speaking, as a\nrule of thumb an Organism is the composition of Molecules that allow parameterization for\nbusiness or domain-specific logic (e.g. the actual ",[136,328,329],{},"awesome_api"," configuration).\nTherefore, in comparison with the previous, Organisms (usually) have a lower\nlevel of generalization since they are business-specialized modules.",[16,332,333,334,336],{},"Iterating over our implementation example, the Organism would implement the\n",[136,335,329],{},", creating the following resources:",[81,338,339,342,345,348],{},[84,340,341],{},"AWS Lambda function;",[84,343,344],{},"AWS API Gateway;",[84,346,347],{},"TLS Certificate on AWS ACM;",[84,349,350],{},"DNS record on AWS Route53.",[16,352,353],{},"By implementing the previous examples as organisms we:",[107,355,356,359,362],{},[84,357,358],{},"reduce the amount of boilerplate code;",[84,360,361],{},"foster reusability of modules;",[84,363,364],{},"provide a simple interface for non-operators to manage TF code.",[16,366,367,368,371,372,375],{},"When you sum it all up, you will notice that it is ",[33,369,370],{},"all about autonomy"," and\n\"DevOps\" through encouragement of self-service Ops. One wouldn't need to know a\nlot about Terraform to grab a module and pass some parameters to it, followed by\na code review process Operators and Software Developers can manage the\nInfrastructure in harmony, ",[33,373,374],{},"together",". (:",[128,377,379],{"id":378},"code-exponentiation-what","Code Exponentiation? What?",[16,381,382,383,388],{},"Read that as a dramatization of the ",[20,384,387],{"href":385,"rel":386},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDuplicate%5Fcode",[24],"\"code duplication\""," term.",[16,390,391,392,61],{},"When it comes to Infrastructure as Code, there is no easy way around the jungle\nof resources that grows over time. Fast pacing tech companies are \"moving fast\nand breaking things\", oftentimes the Operators are worried about a massive\namount of challenges at once: keep the servers up and running, with a consistent\nresponse time, low error rate, and all that ",[20,393,396],{"href":394,"rel":395},"https:\u002F\u002Fsre.google\u002Fsre-book\u002Ftable-of-contents\u002F",[24],"playbook from Google's SRE wisdom",[16,398,399,400,403,404,407],{},"All things considered, a good Infrastructure as Code design is generally\na first-world problem. However, as the time passes it evolves into a real issue\nthat slows down the implementation of resources as code. Either that or there\nwill be a ",[33,401,402],{},"huge ton"," of copy+paste to keep up with the pace, followed by a\nroutine of find+replace when changes are applied, ",[52,405,406],{},"then"," harder to track pull\nrequests and slower code reviews.",[16,409,410,411,413,414,416],{},"Lets take our ",[136,412,329],{}," example and scale it up to multiple environments\nfollowed by a second ",[136,415,329],{},":",[157,418,423],{"className":419,"code":421,"language":422,"meta":163},[420],"language-text",".\n├── development\n│   ├── an-awesome-api\n│   │   └── main.tf\n│   └── another-awesome-api\n│       └── main.tf\n├── staging\n│   ├── an-awesome-api\n│   │   └── main.tf\n│   └── another-awesome-api\n│       └── main.tf\n└── production\n    ├── an-awesome-api\n    │   └── main.tf\n    └── another-awesome-api\n        └── main.tf\n","text",[136,424,421],{"__ignoreMap":163},[16,426,427,428,284],{},"Note that this directory structure is inspired on the proposed ideas from the\n[Terraform best practices post](",[429,430],"binding",{"value":431},"\u003C relref \"terraform-best-practices\" >",[16,433,434],{},"In order to replicate the configuration and ensure consistency, the following is\nway simpler to implement (and review) than copy+paste huge chunks of Terraform\ndefinitions",[157,436,439],{"className":437,"code":438,"language":162,"meta":163},[160],"module \"awesome_api\" {\n  source = \"path\u002Fto\u002Fmodules\u002Forganisms\u002Faws_lambda_with_api_gateway\"\n  version = \"~> 1.0\"\n\n  domain_name = \"record.example.com\"\n  zone_name   = \"example.com.\"\n\n  lambda_functions = [\n    # Index 0 -- An Awesome Lambda Function, used for POST\n    {\n      name        = \"an-awesome\"\n      description = \"An Awesome lambda function for the Awesome API Gateway\"\n      handler     = \"an_awesome.lambda_handler\"\n      runtime     = \"python3.8\"\n    },\n    # Index 1 -- Another Awesome Lambda Function, used as $default\n    {\n      name        = \"another-awesome\"\n      description = \"Another Awesome lambda function for the Awesome API Gateway\"\n      handler     = \"another_awesome.lambda_handler\"\n      runtime     = \"python3.8\"\n    },\n  ]\n\n  api_gateway_name = \"awesome-api-gateway\"\n  api_gateway_description = \"An Awesome API Gateway\"\n\n  api_gateway_integrations = {\n    \"POST \u002F\" = {\n      lambda_function_index  = 0\n      payload_format_version = \"2.0\"\n    }\n\n    \"$default\" = {\n      lambda_function_index = 1\n    }\n  }\n}\n",[136,440,438],{"__ignoreMap":163},[11,442,444],{"id":443},"conclusion","Conclusion",[16,446,447],{},"At the end of the day we get an ugly Terraform state containing many",[157,449,454],{"className":450,"code":452,"language":453,"meta":163},[451],"language-ruby","module.something.module.something_else.module.yet_another_thing...\n","ruby",[136,455,452],{"__ignoreMap":163},[16,457,458],{},"But the productivity boost gained by merging modules based on context is a worth\ninvestment. Especially for huge Terraform repositories with multiple teams\ncollaborating and managing a lot of resources.",[16,460,461,462,61],{},"Cross-team collaboration is fostered by applying the Atomic Design methodology\nfor Terraform modules, code reusability becomes an important factor over\ncopy+paste and the repository gravitates towards the ",[20,463,466],{"href":464,"rel":465},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDon%27t%5Frepeat%5Fyourself",[24],"DRY principle",[11,468,470],{"id":469},"same-post-different-places","Same post, different places",[81,472,473,481,488],{},[84,474,475,480],{},[20,476,479],{"href":477,"rel":478},"https:\u002F\u002Fwww.reddit.com\u002Fr\u002FTerraform\u002Fcomments\u002Fpd708z\u002Fterraform%5Fmodules%5Fatomic%5Fdesign\u002F",[24],"reddit.com: Terraform Modules: Atomic Design - r\u002FTerraform",";",[84,482,483,480],{},[20,484,487],{"href":485,"rel":486},"https:\u002F\u002Fdev.to\u002Fmacunha\u002Fterraform-modules-atomic-design-3i7m",[24],"dev.to: Terraform Modules: Atomic Design - DEV Community",[84,489,490,480],{},[20,491,494],{"href":492,"rel":493},"https:\u002F\u002Fweekly.tf\u002Fissues\u002Fweekly-tf-issue-51-terraform-atomic-design-ec2-image-builder-736257",[24],"weekly.tf: #51 - Terraform Atomic Design, EC2 Image Builder",{"title":163,"searchDepth":496,"depth":496,"links":497},2,[498,499,506,507],{"id":13,"depth":496,"text":14},{"id":101,"depth":496,"text":102,"children":500},[501,503,504,505],{"id":130,"depth":502,"text":131},3,{"id":219,"depth":502,"text":220},{"id":308,"depth":502,"text":309},{"id":378,"depth":502,"text":379},{"id":443,"depth":496,"text":444},{"id":469,"depth":496,"text":470},"2021-06-29T00:00:00+02:00","Adapting the Atomic Design methodology to Infrastructure as Code components to help foster code reusability, ease of maintenance and agile development of the infrastructure. Creates standardization, validates inputs and brings the Terraform definitions closer to the developers (self-service Ops).","md",{},true,"\u002Fposts\u002F2021\u002F06\u002Fterraform-atomic-design",{"title":5,"description":509},"terraform-atomic-design","posts\u002F2021\u002F06\u002Fterraform-atomic-design",[518,519,520],"infrastructure-as-code","cloud","terraform","51i47DRdlOrtRDt7XIlsZ2X7XP5klw9_dS9yyn5J_3A",1778441743697]