The AaC Style Guide

This style guide documents a set of coding standards for the AaC Domain-Specific Language. This standard is implemented in the Core Specification and first-party plugins. Users are not required to implement this style, but it is recommended that users leverage a consistent style and standard for their models. Consistent styling reduces complexity and overhead for users as they don’t have to manage the idiosyncrasies of different naming conventions for user libraries or plugins.

Declaring Multiple Definitions Per AaC File

The AaC DSL is based on YAML, and as such, cannot support multiple declarations of the same root key in the YAML document (indicated by the special YAML document separator token ---). If definitions are not separated by a document separator, then AaC will be unable to correctly ingest those definitions in the AaC file.

Correctly defining multiple definitions in the same file:

model:
    name: SampleModel
---
model:
    name: AnotherModel
---
enum:
    name: SampleEnum
    values:
        - ENUM_VALUE

Incorrectly leaving out the separator will cause parsing errors and lacks visual breaks between definitions:

model:
    name: SampleModel

model:
    name: AnotherModel

enum:
    name: SampleEnum
    values:
        - ENUM_VALUE

Naming Convention

Every entry in AaC is referred to as a definition, and definitions are the basic unit of modeling in AaC. Definitions are used to define data structures, enumeration types, models, and modeled system components. As such, they have several distinct conventions to give users a quick and low-effort way of distinguishing between the various parts of definitions. These distinctions include definition names, fields and attributes, enumeration values, and even core language components such as root keys and primitive value types.

Definition Fields and Attributes

Due to the self-defining nature of AaC, definitions are just instances and compositions of fields and attributes defined by other schema definitions.

In order to maintain a consistent experience for users interacting with these data structures, users should use camelCase for field names.

The following definitions follow the style of naming fields and attributes in camelCase.

enum:
    name: ExampleSubStructure
    fields:
        - name: msgContent
          type: string
---
schema:
    name: ExampleInterfaceMessage
    fields:
        - name: msgMetadata
          type: string
        - name: msgData
          type: ExampleInterfaceMessage

Users leveraging the ExampleInterfaceMessage will have a data structure with fields such as ExampleInterfaceMessage.msgMetaData and ExampleInterfaceMessage.msgData.msgContent. Alternatively, if a user didn’t follow the style for fields and attributes, our example definitions might look like:

enum:
    name: ExampleSubStructure
    fields:
        - name: msg_content
          type: string
---
schema:
    name: ExampleInterfaceMessage
    fields:
        - name: msg_Metadata
          type: string
        - name: MessageData
          type: ExampleInterfaceMessage

The resulting data structure ExampleInterfaceMessage will now have an inconsistent naming convention in its structure with fields such as ExampleInterfaceMessage.msg_Metadata and ExampleInterfaceMessage.MessageData.msg_content. The inconsistent naming of fields can easily spiral into data structures and models that are hard to navigate or interact with for users and plugin developers.

Definition Names

Because definition names are treated as unique identifiers they should adhere to PascalCase, also known as UpperCamelCase. Applying UpperCamelCase exclusively to definition name’s allows users to quickly distinguish a reference to a definition (UpperCamelCase) from a reference to an attribute or field within a definition (camelCase). Due to AaC’s heavy use of definition references, distinctly naming definition names as UpperCamelCase makes it clear to users that the referenced object is another definition.

Example reference of definitions by name:

enum:
    name: SampleEnum
    values:
        - ENUM_VALUE
---
schema:
    name: ExampleDataStructure
    fields:
        - name: enumField
          type: SampleEnum

If a user didn’t follow convention, an example of an inconsistently styled model could look something like:

enum:
    name: sample_enum
    values:
        - ENUM_VALUE
---
schema:
    name: ExampleDataStructure
    fields:
        - name: enumField
          type: sample_enum

In this example, a user unfamiliar with the project can easily mistake simple_enum as a primitive type.

Enumeration Values

Enumeration values in AaC are all UPPERCASE, following the common style for enumeration values and constants in programming languages such as Python and Java.

enum:
    name: SampleEnums
    values:
     - VALUE_1
     - VALUE_2
     - VALUE_3

Primitive Types

Primitive types in AaC are defined by the enumeration values in the Primitives definition. Each enum value represents a uniquely special set of enumeration values that define the core AaC language’s primitive types. Users who extend Primitives should maintain the same style of snake_case and keep primitive type names succinct: in other words, names should be descriptive, but short.

Root Keys

A schema may define a model structure that you would want to use at the root of your YAML declaration. Internal examples from AaC include schema, model, enum, and various other definitions. If you need the ability to define your own custom YAML root key, simply populate the optional root entry in your schema definition for the new item. AaC will automatically recognize this as a valid YAML root key for items of the type defined by your new schema.

Organizing Imports

Each AaC file has a file-wide import declaration definition. Using an import definition allows users to reference definitions provided via other user files and can be used to decompose large, complex models into multiple files.

Because import declarations are file-wide, these clauses should be the first clause in AaC files. There is no internal logic that dictates the order of the entries in an AaC file, but maintaining the import clause as the first definition in AaC files makes the file-wide dependencies immediately visible and informs readers on the dependencies of file. Additionally, while it’s possible to include multiple import definitions in a single file, the preferred approach is to list all imported files in a single import definition in each file.

An example file with an import clause at the top:

import:
    files:
        - ../interface/service_a_messages.aac
---
model:
    name: ExampleService
    behavior:
        - name: ServiceBehavior
          type: request-response
          input:
            - name: userRequest
              type: ServiceARequestMessage

In larger AaC files, if an import definition is included, it should be placed at the top of file to make it obvious to users what other files and definitions the AaC file may rely on. If placed anywhere other than at the top of the file, not only are file dependencies obfuscated for readers, but the likelihood of users declaring another import definition elsewhere in the file increases.

Decomposing Models Across Files

Users who have experience managing large code files will quickly recognize that large models can be difficult to manage in large AaC files. To this end, it’s recommended that you distribute your models into logical, related groups in AaC files that are then imported by the AaC files that rely on them. For instance, if you have a system model ExampleSystemModel with schema definitions defining internal data structures like inter-service messages or external API data structures, these schema definitions can be sequestered into a separate AaC file that solely contains these interface data structures. This file should be imported by the ExampleSystemModel, which references those interface data structures in its interactions and behaviors. Breaking models and their components into related or logical groups can also improve the navigation and organization of AaC projects.

The import keyword supports a list of relative or absolute paths to other user-defined files in the overall project. Users are not confined to a working directory or a single-flat directory for their AaC files. The files can be distributed in directories and subdirectories, or even alongside their related components in existing software projects.

It’s recommended that your AaC file structure avoids circular imports (e.g. File A imports File B, File B imports File C, and File C imports File A). AaC has some basic internal checks to de-conflict circular imports, but circular imports may cause unexpected behavior and indicate poor project organization.