> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formae.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Reuse infrastructure with modules

> Structure formae code into Pkl modules and classes so you share configuration, compose resources, and write a pattern once instead of copying it across formae.

As your infrastructure grows, a single forma file stops scaling. The same
stack, target, and tagging conventions get copied from file to file, and a
networking pattern you got right once gets pasted into the next project. formae
files are [Pkl](https://pkl-lang.org), so you have Pkl's own tools for reuse:
put shared values in a module, group related resources into a class, and
compose those pieces into the forma you apply.

This guide covers the general reuse mechanics: sharing configuration, grouping
resources, and composing modules. For a complete worked class that a platform
team hands to developers as a self-service offering, see
[Build self-service infrastructure](/documentation/guides/build-self-service-infrastructure).

## Share configuration with a vars module

Most projects reuse the same stack and target across several formae. Put those
in a plain Pkl module and import it wherever you need them.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// vars.pkl
import "@formae/formae.pkl"
import "@aws/aws.pkl"

region = "us-east-1"

stack: formae.Stack = new {
  label = "lifeline"
  description = "Lifeline infrastructure"
}

target: formae.Target = new formae.Target {
  label = "default"
  config = new aws.Config {
    region = module.region
  }
}
```

Import it and reference the shared values from any forma:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// main.pkl
amends "@formae/forma.pkl"
import "@formae/formae.pkl"
import "@aws/s3/bucket.pkl"
import "./vars.pkl"

forma {
  vars.stack
  vars.target

  new bucket.Bucket {
    label = "artifacts"
    bucketName = "lifeline-artifacts"
  }
}
```

Now the stack and target are defined once. Change the region in `vars.pkl` and
every forma that imports it picks up the new value.

## Group resources into a class

When several resources always travel together, a VPC with its subnets and route
table, wrap them in a Pkl class. The class takes its inputs as typed fields,
builds the resources with `hidden`, and exposes them as a single `resources`
listing.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// infrastructure/networking.pkl
import "@formae/formae.pkl"
import "@aws/ec2/vpc.pkl"
import "@aws/ec2/subnet.pkl"
import "@aws/ec2/securitygroup.pkl"

class Networking {
  name: String
  vpcCidr: String
  region: String

  hidden vpc: vpc.VPC = new {
    label = "\(name)-vpc"
    cidrBlock = vpcCidr
    enableDnsHostnames = true
    enableDnsSupport = true
  }

  hidden publicSubnet: subnet.Subnet = new {
    label = "\(name)-public-subnet"
    vpcId = vpc.res.id
    cidrBlock = "10.0.1.0/24"
    availabilityZone = "\(region)a"
  }

  hidden appSecurityGroup: securitygroup.SecurityGroup = new {
    label = "\(name)-app-sg"
    vpcId = vpc.res.id
    groupDescription = "Application security group"
  }

  hidden resources: Listing<formae.Resource> = new {
    vpc
    publicSubnet
    appSecurityGroup
  }
}
```

A few things worth noting:

* `hidden` keeps each resource internal to the class. Only the `resources`
  listing needs to be consumed from outside.
* `vpc.res.id` is a [resolvable](/documentation/concepts/resolvable). Inside the
  class the subnet references the VPC's ID, and formae works out that the VPC
  has to be created first.
* The class exposes one thing worth reading: `resources`, a listing formae knows
  how to apply.

Wire the class to your inputs and spread its resources into the forma. The `...`
operator expands the listing into individual resources:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// main.pkl
amends "@formae/forma.pkl"
import "@formae/formae.pkl"
import "./infrastructure/networking.pkl" as nw
import "./vars.pkl"

local network = new nw.Networking {
  name = "lifeline"
  vpcCidr = "10.0.0.0/16"
  region = vars.region
}

forma {
  vars.stack
  vars.target
  ...network.resources
}
```

## Compose classes

Layered infrastructure is where classes pay off. Build the networking layer,
then pass its resources into a class that needs them. A field typed as a
resource accepts an instance produced by another class, and the receiving class
reads properties off it with `.res`.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// A database layer that depends on the networking layer
class Database {
  name: String
  vpc: vpc.VPC          // Passed in from the networking class
  subnet1: subnet.Subnet
  subnet2: subnet.Subnet

  hidden dbSubnetGroup: dbsubnetgroup.DBSubnetGroup = new {
    label = "\(name)-db-subnet-group"
    dbSubnetGroupDescription = "Subnet group for \(name)"
    subnetIds {
      subnet1.res.subnetId
      subnet2.res.subnetId
    }
  }

  hidden resources: Listing<formae.Resource> = new {
    dbSubnetGroup
  }
}
```

Instantiate both layers, hand the networking resources to the database, and
spread each layer's resources into the forma:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
local network = new nw.Networking {
  name = "lifeline"
  vpcCidr = "10.0.0.0/16"
  region = vars.region
}

local database = new db.Database {
  name = "lifeline"
  vpc = network.vpc
  subnet1 = network.publicSubnet
  subnet2 = network.privateSubnet
}

forma {
  vars.stack
  vars.target
  ...network.resources
  ...database.resources
}
```

### Reference parent fields with outer

When one class nests another, use `outer` inside the inner definition to reach a
field on the enclosing class. This lets a top-level class thread a single input,
like a region or an account ID, down into the resources it builds without
repeating it.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
class Platform {
  name: String
  region: String

  hidden networking: nw.Networking = new {
    name = outer.name        // Reference the Platform's name
    region = outer.region    // Reference the Platform's region
    vpcCidr = "10.0.0.0/16"
  }
}
```

## Prefer functions for straightforward creation

If a module just produces resources from inputs without composing layers, a
function is lighter than a class. A function takes its inputs as arguments and
returns either a single resource or a `Listing`.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// infrastructure/network.pkl
import "@aws/ec2/vpc.pkl"
import "@aws/ec2/subnet.pkl"

function vpc(name: String): vpc.VPC = new vpc.VPC {
  label = "\(name)-vpc"
  cidrBlock = "10.0.0.0/16"
  enableDnsHostnames = true
}

function privateSubnets(name: String): Listing = new Listing {
  new subnet.Subnet {
    label = "\(name)-private-1"
    vpcId = vpc(name).res.id
    cidrBlock = "10.0.1.0/24"
  }
  new subnet.Subnet {
    label = "\(name)-private-2"
    vpcId = vpc(name).res.id
    cidrBlock = "10.0.2.0/24"
  }
}
```

Call the functions in the forma, spreading any that return a listing:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
import "./infrastructure/network.pkl" as network

forma {
  vars.stack
  vars.target
  network.vpc("lifeline")
  ...network.privateSubnets("lifeline")
}
```

Reach for a class when you need to hide internal resources behind an interface
or pass resources between layers. Reach for a function when creation follows a
clear flow and there is nothing to encapsulate.

## Apply as usual

Modules and classes are an authoring convenience. By the time formae sees your
forma, the class fields and function calls have resolved to a flat set of
resources, so applying works exactly as it does for a single-file forma.

```bash theme={"languages":{"custom":["/languages/pkl.json"]}}
formae apply --mode reconcile main.pkl
```

<div className="fa-term">
  <div className="fa-term-body">  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}}>formae</span> <span style={{fontWeight:'600'}} /><span style={{color:'rgb(255,133,51)',fontWeight:'600'}}>apply · reconcile</span>                                                                                main.pkl<br /><span style={{color:'rgb(85,85,102)'}}>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span><br /><span style={{color:'rgb(85,85,102)'}} />  <span style={{color:'rgb(232,232,232)'}}>+</span> 15 <span style={{color:'rgb(232,232,232)'}}>create</span><br /><br />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(255,133,51)',fontWeight:'600'}}>▌ Targets</span><br />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}}>Operation ▲   </span><span style={{color:'rgb(170,170,170)'}}>Label</span><br />  <span style={{color:'rgb(232,232,232)'}}>+ create      aws-target</span><br /><br />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(255,133,51)',fontWeight:'600'}}>▌ Stacks</span><br />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}}>Operation ▲   </span><span style={{color:'rgb(170,170,170)'}}>Label</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline</span><br /><br />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(255,133,51)',fontWeight:'600'}}>▌ Resources</span><br />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}} /><span style={{color:'rgb(232,232,232)',fontWeight:'600'}}>Operation ▲   </span><span style={{color:'rgb(170,170,170)'}}>Label                                                       Type</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-vpc                                                </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::VPC</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-igw                                                </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::InternetGateway</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-igw-attachment                                     </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::VPCGatewayAttachment</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-public-subnet-1                                    </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::Subnet</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-public-subnet-2                                    </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::Subnet</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-public-rt                                          </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::RouteTable</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-public-route                                       </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::Route</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-public-subnet-1-assoc                              </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::SubnetRouteTableAssociation</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-public-subnet-2-assoc                              </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::SubnetRouteTableAssociation</span><br />  <span style={{color:'rgb(170,170,170)'}}>+ create      </span><span style={{color:'rgb(129,209,219)'}}>lifeline-alb-sg                                             </span><span style={{color:'rgb(170,170,170)'}}>AWS::EC2::SecurityGroup</span><br /><span style={{color:'rgb(153,153,153)'}}>      ↓ show 10 more (3 remaining)</span><br /><br /><span style={{color:'rgb(85,85,102)'}}>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span><br /><span style={{color:'rgb(85,85,102)'}} />  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>↑↓</span><span style={{color:'rgb(153,153,153)'}}>: select</span>  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>space</span><span style={{color:'rgb(153,153,153)'}}>: expand</span>  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>→←</span><span style={{color:'rgb(153,153,153)'}}>: column</span>  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>s</span><span style={{color:'rgb(153,153,153)'}}>: sort</span>  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>y</span><span style={{color:'rgb(153,153,153)'}}>: confirm</span>  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>q</span><span style={{color:'rgb(153,153,153)'}}>: abort</span> This operation will create 1 stack(s), create<br />1 target(s) and create 13 resource(s).  Do you want to continue? (y/N)  <span style={{fontWeight:'600'}} /><span style={{color:'rgb(129,209,219)',fontWeight:'600'}}>?</span><span style={{color:'rgb(153,153,153)'}}>: help</span></div>
</div>

formae shows the plan and asks for confirmation before making any changes, then
streams the run in a live view. Pass `--yes` to skip the confirmation prompt in
a CI/CD job.

## See also

* [Build self-service infrastructure](/documentation/guides/build-self-service-infrastructure): a full class-based offering exposed to developers through property flags.
* [Resolvable](/documentation/concepts/resolvable): how `.res` references let resources depend on each other, including across classes.
* [Properties](/documentation/concepts/properties): parameterize a module's inputs as CLI flags.
* [Stack](/documentation/concepts/stack): how the resources you compose are grouped and reconciled together.
* [Apply modes](/documentation/concepts/apply-modes): reconcile versus patch once your composed forma is ready to apply.
