---
sidebar_label: Services
doc_id: 7f852f91-a92b-442c-80b0-8585229092ba
description: >-
  Services are modeled dependencies like databases or caches that streamline
  provisioning and standardize infrastructure.
keywords:
  - infrastructure-as-code
  - service provisioning
  - dependencies
  - specifications
  - standardization
---

# Services

## What is a service?

Services can be databases, caches, load balancers, or any other dependency an application needs to run.
They can live on-premises or in the cloud. Because you model services, you can also define dependencies like
monitoring, email, or even other applications (for example, a banking core).

## Why should I use services?

Using services helps you:

- **Speed up provisioning and reduce manual work.** Services cut the back-and-forth with specialized teams, making infrastructure easier to set up.

- **Let developers focus on code.** Infrastructure is handled by experts through Infrastructure-as-Code (IaC), so developers don’t need to worry about the details.

- **Keep infrastructure consistent.** Services encourage standardization, helping you avoid one-off custom setups that cause drift.

- **Make operations safe and efficient.** Tasks like querying a database can be handled through custom actions, combined with policies and approvals. This makes operations fast, secure, and trackable.


## Main concepts

### Specifications

Also known as "specs", these define the name, category, standard actions, and allowed parameters for the services you’ll provision.

You can think of specifications and services as being like classes and objects in object-oriented languages: the spec
defines how the service is created.

Here's an example of a service specification:

```json
{
  "id": "abcd1324-53ab-b41b-a28f-55ca9c95ef1",
  "name": "SQS Queue",
  "slug": "sqs-queue",
  "type": "dependency",
  "visible_to": [
    "organization=1234:account=*"
  ],
  "dimensions": {
    "country": {
      "required": true
    },
    "environment": {
      "required": true
    }
  },
  "assignable_to": "dimension",
  "created_at": "2024-01-12T13:14:15.199Z",
  "updated_at": "2025-01-12T13:14:15.199Z",
  "use_default_actions": true,
  "attributes": {
    "schema": {
      "type": "object",
      "required": [
        "queue_arn",
        "queue_url"
      ],
      "properties": {
        "queue_arn": {
          "type": "string",
          "title": "Queue ARN",
          "export": true,
          "pattern": "^arn:aws:sqs:[a-z0-9-]+:[0-9]+:([a-zA-Z0-9_-]+\\.fifo|[a-zA-Z0-9_-]+)$",
          "visibleOn": [
            "read",
            "update"
          ],
          "editableOn": []
        },
        "queue_url": {
          "type": "string",
          "title": "Queue URL",
          "export": true
        },
        "account_id": {
          "type": "string",
          "title": "AWS Account ID",
          "config": {
            "key": "aws.account_id"
          },
          "export": false,
          "readOnly": true
        },
        "queue_type": {
          "type": "string",
          "title": "Queue Type",
          "export": false,
          "readOnly": true
        },
        "delay_seconds": {
          "type": "integer",
          "title": "Delay Seconds",
          "maximum": 900,
          "minimum": 0,
          "description": "The time in seconds to delay the delivery of new messages (0-900). Only available for standard queues."
        },
        "dead_letter_arn": {
          "type": "string",
          "title": "Dead Letter Queue ARN",
          "export": true,
          "pattern": "^arn:aws:sqs:[a-z0-9-]+:[0-9]+:([a-zA-Z0-9_-]+\\.fifo|[a-zA-Z0-9_-]+)$",
          "visibleOn": [
            "read"
          ],
          "editableOn": []
        },
        "max_message_size": {
          "type": "integer",
          "title": "Maximum Message Size",
          "maximum": 262144,
          "minimum": 1024,
          "description": "The maximum size of messages in bytes (1KB to 256KB)"
        },
        "visibility_timeout": {
          "type": "integer",
          "title": "Visibility Timeout",
          "maximum": 43200,
          "minimum": 0,
          "description": "The length of time (in seconds) that a message will be invisible to other receiving components",
          "visibleOn": [
            "read",
            "update"
          ],
          "editableOn": [
            "update"
          ]
        },
        "message_retention_seconds": {
          "type": "integer",
          "title": "Message Retention Period",
          "maximum": 1209600,
          "minimum": 60,
          "description": "The number of seconds to retain messages (60s to 14 days)"
        },
        "receive_wait_time_seconds": {
          "type": "integer",
          "title": "Receive Wait Time",
          "maximum": 20,
          "minimum": 0,
          "description": "The time to wait for messages on a ReceiveMessage call (0-20 seconds)"
        }
      },
      "additionalProperties": false
    },
    "values": {}
  },
  "selectors": {
    "category": "Messaging Services",
    "imported": false,
    "provider": "AWS",
    "sub_category": "Message Queue"
  }
}
```

There are three kinds of specifications:

- Service specifications
- Link specifications
- Action specifications

This will make more sense in the following sections as we introduce **links** and **actions**.

### Actions

Actions define **_what_** you can do with a service. Most commonly, these include:

- Create
- Update
- Delete

These standard actions are autogenerated from the service specification's attributes schema. New specifications enable this by default (`"use_default_actions": true`); specifications created before this default changed keep the value they were saved with. This lets you control what can be done with a service, and when, without manually defining action behavior.

As shown in the spec example above, configuring the behavior of default actions in the service spec might look like this:

```json
{
  "use_default_actions": true,
  "attributes": {
    "schema": {
      "my_property": {
        "type": "string",
        "visibleOn": ["create", "update"],
        "editableOn": ["create"]
      }
    }
  }
}
```

In this case, the property `my_property` will only be visible when creating or updating a service instance, and can only be edited on creation.

On top of these, you can define **custom actions**. They have the same capabilities as the default actions, but are opaque to nullplatform. You create them using **action specifications** and get the same flexibility, but nullplatform doesn’t interpret them the same way. They’re ideal when you need to go beyond the default lifecycle, for example:

- Send message to queue
- Purge cache
- Fetch transaction by ID


:::warning Actions and Action Specifications are different things
In the same way you have service specifications (~class) and services (~objects), you
have action specifications and concrete actions created from those specifications.
:::

#### How are actions executed?

An action definition is just JSON that defines the interface to create the action and receive results, but the
actual execution typically happens on a runner (for example, GitHub Actions).

The flow looks like this:

```mermaid
flowchart
    A("<b>User</b><br>Requests an action to be executed")
    A --> n2["<b>nullplatform</b><br>Validate parameters, create action, find notification channel and execute webhook"]
    n2 --> n3["<b>Github, Gitlab, or a custom runner</b><br>Receives the notification event for the action,<br> provision or execute actions using OpenTofu,<br> Javascript, or any other language, and then posts action results"]
    n3 --> n4["<b>nullplatform</b><br>Receives and display action results"]

```

### Links

Once you have your service running, you’ll want to connect your applications to it. This is done through **links**.

Here are some examples:

- For a **Queue**, you’ll have links to scopes that are message **producers** or **consumers**.
- In the case of a **Database**, you’ll have scopes that can **read**, **write**, or **read and write** to the
  database.
- If your service is a **Load balancer** or **API Gateway**, you can use links to **grant other applications access** to
  your
  application.

#### Links and parameters

When you link a service (for example, a Database) to an application, you’ll likely create parameters for that application to
actually link or consume the service. For a database, you might export the DB connection string, username, and password
as parameters, so your application receives them at runtime and can interact with the service.

You’ll see in the following sections that you can have **secret parameters** for your service. A parameter marked as
secret will also be marked as secret in your application’s parameters list.

Also, these rules will apply when linking a service to scopes or dimensions:

- **When linking to a scope**, the application’s parameters are created with values for that scope only.
- **When linking to dimensions**, the application’s parameters are created for those dimensions, so any scope
  from those dimensions can use the service.

:::note Service parameters are read-only for your application
When a service creates application parameters, those parameters show as "read-only", meaning
only the service can change their content.
:::

#### Link types

The application that accesses the service might use it for different purposes. For example, a database can be used to
read, write, or read/write. The use you choose is expressed through **link types**.

When you create different link types, we ask which kind of link to create. That lets us display the
right creation form and send the correct link creation action with its parameters.

#### Is it mandatory to have links?

No. While it’s a good practice, you can provision a service on creation and make it automatically
available to your application.

:::warning Links and Link Specifications are different things
The same way you have service specifications (~class) and services (~objects), you
have link specifications and concrete links created from those specifications.
:::

### Summary

| Entity                | Description                                                                                           |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| Service specification | Defines the name, category, visibility, default actions, and parameters for the services it defines.  |
| Service               | A service instance for a specific service specification. Can be created, updated, or destroyed.       |
| Action specification  | Defines the type (create, update, delete, custom) of actions along its parameters.                    |
| Action                | An action instance that runs on a runner such as GitHub actions.                                      |
| Link specification    | Defines how the service can be linked to scopes. You can have different types of links for a service. |
| Link                  | A link instance that can be created, updated, or destroyed.                                           |

## How do I design a service?

#### Low-level cloud services vs. higher-level abstractions

While there’s a tendency to replicate cloud services as nullplatform services, we recommend grouping cloud services
into a single, higher-level abstraction. For example, if you need processes to produce messages that others consume,
you could create a single Publisher-Subscriber service that provisions SNS and SQS queues behind the scenes.
These higher-level abstractions also shield developers and users from low-level details needed
to glue the cloud services together.

#### Choose the essential service options to expose to users

The benefits of creating services come from standardization and reduced complexity for users. So you’ll
want to narrow the available options to what really matters. While the underlying service might allow fine-grained
tuning of options like "encryption", you’ll likely apply your own security criteria rather than relying on
user discretion.

#### Service and link visibility

You can narrow service availability by specifying which [NRN](/docs/NRN) has access. You can also specify
whether the service can be linked to dimensions and/or scopes.

#### Summary: DOs and DON'Ts

Let's summarize these recommendations like this:

- ✅ **DO** favor services that simplify complexity by avoiding unnecessary parameters.
- ✅ **DO** use JSON schema to validate parameters _before_ running actions.
- ✅ **DO** leverage IaC tools such as OpenTofu, hiding complex details from end users.
- ✅ **DO** design user interfaces that feel intuitive and natural using UI schemas.
- ❌ **DON'T** create a one-to-one copy of the cloud services you have. For example, try to aggregate SNS+SQS into a more user-friendly "Publisher-Subscriber" model.
- ❌ **DON'T** create complex interactions that require end users to have a lot of context or follow steps in a specific
  order.

## Next steps

Now that you have the basic concepts on services, read the subsections for a thorough guide on
how you can go from zero to production with services.

Here are some quick links that can be useful to jump to:

- [How to define your services](/docs/services/craft-a-service/service-specs)
- [How to import existing services](/docs/services/import-services)
- [Provisioning your services](/docs/services/provisioning)
