Replies: 2 comments 1 reply
-
|
Really appreciate the detailed exploration on this @Happy0. Still wrapping my head around the broader implications so a few points and clarifying questions to start.
We do this in the IaC layer. It would be good to understand what kind of use cases need this in the API layer.
Agree.
Good suggestion
I've so far resisted this because to avoid more complex code when unpacking types e.g. https://www.pulumi.com/docs/concepts/inputs-outputs/. This kind of code feels like it very much belongs in the IaC layer. But there are inevitably use cases in the API layer. Is it possible to support them without losing the current feel of the framework.
I like this structure. One point of clarification though: the NotationResource, in my mind at least, represents the resource declared in the API layer. API integration, for example, makes sense as a logical grouping, but is not a resource declared in the API layer. Perhaps this could be called an InferredNotationResource.
The key advantage of this is less for the orchestrator and more for contributors (or developers who use the IaC layer directly). Having the dependencies defined statically, constrains them to only connecting infra resources that are compatible.
For visualisation, I prefer your grouping. I also like seeing which output values are passed around but it's probably a bit much to take in in on the first glance. Useful when drilling down into the detail. Not a big problem transforming the graph for visualisation though. What do you mean by indirection?
I agree. This replaces uncertainty (which param am I passing in here?) with a guarantee (just give us the whole resource and we'll figure it out).
Do you mean not having to use
Also need to explore if a new approach to outputs opens us up to not being able to generate the orchestration graph at orchestration time. |
Beta Was this translation helpful? Give feedback.
-
|
Picking up on:
A big use case for this would be a stack deployed by Notation (e.g. an API) that is consumed by another app deployed elsewhere. That app might be in the same monorepo. Ideally I'd like type interop between the backend API and the front-end app, even if though they are deployed elsewhere. Putting to one side versions and coordinating deploys, let's ask: how do I get the outputs from my API in my front-end app? I'd like to put forward a proposal to use NPM packages as a way to expose an interface from a Notation stack to another app (or another Notation stack). NPM packages have a few advantages:
How might this work?
That provides a way to consume stacks in any other JavaScript projects. To provide type interop Notation could provide decorators. For example: // packages/stacks/user-api/index.ts
import { apiClient } from "@notation/aws/clients";
import { userApi } from "./api";
export const userApiClient = apiClient({ api: userApi });And then in your app: // packages/apps/dashboard
import { userApiClient } from "@stacks/user-api";
// this would be typed based on the API routes
const users = await userApiClient.get("/users"); |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Definitions
Resources
An infrastructure entity which is managed (created, configured, updated and deleted) by Notation. In this RFD, I am referring to the high level ‘resource’ exposed to the user from the top level API package (‘aws’ in the case of notation AWS resources) via a function based interface. Examples:
Resource Groups
A resource is often a composition of other infrastructure resources managed by notation internally into ‘resource groups.’ For example, an API gateway route handler internally constructs an AWS lambda service ‘lambda’ function resource and sets up the required permissions to allow the API gateway resource to execute the function as dependencies. See #5 for more details on how Resource Groups work conceptually.
‘ResourceGroup’ is the current return type of Notation resources at the API layer, encapsulating all the ‘inner’ resources constructed to build the notation resource ( https://github.com/notationhq/notation/blob/main/packages/core/src/orchestrator/resource-group.ts#L9 ). A ResourceGroup is sometimes passed to other resources to wire together resources that are dependent on each other - for example an API gateway group passed to a ‘API route’ resource.
Outputs
Meaningful data returned by a resource on successful deployment. For example, an API gateway resource may return the dynamically generated Base URL of the newly deployed HTTP API. An SQS queue resource might return the queue URL (which can be used to enqueue new entries.)
Orchestration Time - The stage where notation builds a representation of all the resources to deployed, making it possible to determine which resources need to be deployed as a dependency of others.
Deploy Time - When notation deploys all the resources defined by the notation app.
API Layer - The ‘top level’ at which the user interacts with notation to use predefined resources (e.g. construct an API gateway, etc.)
IAC Layer - The low level resource definitions that are capable of orchestrating the underlying provider (e.g. AWS.)
Aims
Example Use Cases
Proposed Approach
The proposed approach requires two API level considerations:
Outputs
Treating ResourceGroup as the entry point to a resource that the user will interact with, I propose we add a new ‘outputs’ field that the user can access.
New ResourceGroup Type
Note: parentId allows the orchestrator to work out what ResourceGroup an output came from when it is used as an input. It does not necessarily need to be passed in at construction as it could be calculated in ResourceGroup’s constructor.
Inputs
Until now, a configuration input to a ResourceGroup is passed in as a static value (or computed expression) by the user (e.g. the ‘name’ config field here is a string value):
We need to be able to represent in the type system that input can dynamically come from another resource’s output at runtime.
I propose we introduce a new Notation type that is capable of representing either a value or a value which will be wired in from another resource group’s output during deployment for each config field.
Consider the following type
This isn’t quite sufficient because it is incapable of representing whether the ‘presence’ (required or optional) between the target output and input are compatible. We can redefine the NotationInput type to impose compatibility by defining NotationInput in the following way:
Transformations
I propose the runtime resolved value of a ‘ResourceGroupOutput’ should be transformable at runtime (akin to how Promises are ‘thenable’ to transform to new promises) as there will be cases where the user doesn’t want to use the exact output value of the resource. Example:
We could consider allowing async operations by allowing the ‘transform’ parameter to return a
Promise<R>type.Formalising Notation Resource Interfaces
At present, ResourceGroup has the following fields:
The group’s internal resources can be accessed via the following function:
This is sometimes used to find a resource in the group required to create another low level resource such as the API gateway required to bind a api gateway route resource to:
This resource is then passed to another IAC level resource which accesses its ‘output’ fields at provision time to fetch the necessary data to continue with deployment (via a 'dependencies' field.)
I propose that the details of the internal resources a notation resource (or resource group) is made up of should be opaque (private fields) and only the explicit ‘outputs’ should be visible to be referenced either as ‘implicit’ dependencies to other resources as above or as user defined ‘input values’ to another resource.
To this end, perhaps the ‘ResourceGroup’ type should be renamed to ‘NotationResource’ or similar.
Diagram
Before
flowchart LR subgraph API API_Stage --> API_Gateway end subgraph API_Route_1 API_Gateway_Route_1 --> API_Gateway end subgraph API_Route_2 API_Gateway_Route_2 --> API_Gateway end subgraph Function Integration_Lambda_A --> API_Gateway Permission_Lambda_A --> API_Gateway Integration_Lambda_A --> Lambda_A Permission_Lambda_A --> Lambda_A Role_Attachment --> Role Lambda_A --> Role_Attachment end API_Gateway_Route_2 --> Integration_Lambda_A API_Gateway_Route_1 --> Integration_Lambda_AAfter
flowchart LR subgraph API API_Stage --> API_Gateway subgraph API_OUTPUTS API_GATEWAY_BASE_URI API_GATEWAY_ID end end subgraph API_Route_1 API_Gateway_Route_1 --> API_GATEWAY_ID end subgraph API_Route_2 API_Gateway_Route_2 --> API_GATEWAY_ID end subgraph Function subgraph FUNCTION_OUTPUTS FUNCTION_ARN end Role_Attachment --> Role Lambda_A --> Role_Attachment end subgraph API_GATEWAY_INTEGRATION Integration_Lambda_A --> API_GATEWAY_ID Permission_Lambda_A --> API_GATEWAY_ID Integration_Lambda_A --> FUNCTION_ARN Permission_Lambda_A --> FUNCTION_ARN subgraph INTEGRATION_OUTPUTS INTEGRATION_ID end end API_Gateway_Route_2 --> INTEGRATION_ID API_Gateway_Route_1 --> INTEGRATION_ID subgraph SSM_PARAM API_URL --> API_GATEWAY_BASE_URI endAdvantages
Disadvantages
UX Considerations
Where appropriate, I think we should continue to expect entire ‘NotationResource’ (or ‘ResourceGroups’) as parameters where there are implicit dependencies (such an API route expecting an API resource param) between resources at the API layer.
This is more type safe than expecting the user to pass the ‘raw’ output values required as inputs and acts as a level of documentation.
I think we should expect notation resources to return types that extend the raw ‘NotationResource’ type (e.g. API gateway would return a type named APIGateway even if it was just a type alias for NotationResource.)
However, I think the ‘iac layer’ should accept the ‘low level’ fields it requires rather than introspecting IAC level resources. This allows us to keep the API layer opaque since a NotationResource does not need to expose its internal IaC resources. Additionally, the IaC layer does not need knowledge of the API layer.
Implementation Challenges
Orchestration
Low level ‘resources’ must be capable of accepting the new ‘NotationInput’ types as values where they match a schema field’s field name and presence values. They must be able to represent in the orchestration graph which resource owns these output values (by resource ID.)
This may be challenging as the type level code which transforms a resource schema to the concrete expected types is already quite complex.
We must also be able to detect infinite dependency loops between chains of inputs and outputs and raise an error.
Deployment
We must be able to create and follow a deployment order strategy which deploys resources in the right order to allow outputs to be wired to the inputs of other resources.
We may need to change our deployment strategy as currently resources accept other resources as dependencies rather than their outputs.
Beta Was this translation helpful? Give feedback.
All reactions