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:
{
"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 when you enable them in the service specification using the "use_default_actions": true property. 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, enabling default actions and configuring their behavior in the service spec might look like this:
{
"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
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:
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.
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.
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 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: