Mental model
This page gives a high-level view of Metatype's foundations.
For a hands-on introduction, head over to the basics tutorial and start build your first typegraph.
Why does Metatype exist?
Building great APIs is a thought challenge. Developers usually spend a non-negligible amount of time on low-value added tasks (CRUD generation, data validation, authorization, etc.) and managing deployments. This gives them little time to design great interfaces and experiment with the best technical approaches, eventually increasing the time to delivery and weakening innovation.
Metatype's vision is to enable everyone to build modular API with as little effort as possible. By helping developers to re-use existing systems and APIs, it enables teams to focus on what matters: their expert knowledge in business domain, modelling and technologies. Metatype manage the complex aspects for them, making them productive and innovation-friendly for the next iterations.
How does Metatype work?
When developing a feature, the classical approach is to define what data will be at play, how to transform them, where the execution shall take place and who should be authorized. Instead, Metatype define an abstraction for each of those steps and put the emphasis on composing pre-defined APIs or defining re-usable ones when there is no existing solution.
Classical model | Metatype's model | x | |
---|---|---|---|
What (data) | fixed response defined by the logic | API clients selects what they need from types | |
How (transformations) | ad-hoc code logic | composed data with interchangeable functions | |
Where (execution) | 1 code base + 1 database | orchestrate the request across multiple runtimes | |
Who (authentication) | hard-coded rules or system | request context based and controlled by policies | |
When (event) | request arrival | based on triggers |
This computing model brings numerous advantages:
- it offers multiple runtimes with pre-defined operations and can replace the needs for an ad-hoc backend
- when the project grows, you easily introduce new APIs or break existing ones in smaller parts
- you write complex business logic directly in Typescript, Python or WebAssembly and run them on-demand
- third-parties APIs can be easily integrated, providing you visibility and control over them
- it is interoperable with existing (legacy) systems, and can be introduced step by step
- it can be easily self-hosted in your own infrastructure or customized according to your needs
What's exactly Metatype?
Metatype is an open source platform to author and deploy APIs for the cloud and components eras. It provides a declarative programming model that helps you to efficiently design APIs and focus on the functional requirements.
The runtime embraces WebAssembly (WASM) as a first-class citizen to allow you to write your business logic in the language of your choice and run it on-demand. Those "backend components" are reusable across your stacks and deployable without pipelines or containers.
The platform provides a set of capabilities out of the box:
- create/read/update/delete data in your database
- storing files in your cloud storage
- authenticate users with different providers or using JWTs
- connecting to third-party/internal APIs
And offers an opportunity to climb the one step higher in the abstraction ladder and drastically simplify the building of great APIs and systems!
Metatype is designed to be as simple as possible and horizontally scalable in existing container orchestration solution like Kubernetes. It consists of multiple parts, including:
- Typegraph: a cross-language SDK to manage typegraphs - virtual graphs of types - and compose them
- Typegate: a serverless GraphQL/REST gateway to execute queries over typegraphs
- Meta CLI: a command-line tool to efficiently deploy the typegraphs on the gateway
Core abstractions
Types
Types are the building block of typegraphs. They define a type system describing all data objects processed in Metatype. They can be easily extended to support new data types according to the needs of the application.
t.struct(
{
"id": t.uuid(),
"age": t.integer(),
"cars": t.list(
t.struct(
{
"model": t.string(),
"name": t.string().optional(),
}
)
),
}
)
Analogy in SQL: types are similar to the Data Definition Language (DDL) with the extended capacity of describing any type of data.
Functions
Types can also describe functions and functions define how the input type gets transformed into the output type. The input and output types are similar to a function signature, the runtime + configuration associated to it, to its implementation.
deno = DenoRuntime()
deno.func(
t.struct({"input": t.string()}),
t.string(),
code="({ input }) => `hello ${input}`", # with logic
)
http = HttpRuntime("https://random.org/api")
http.get(
"/flip_coin",
t.struct({}),
t.enum(["head", "tail"]),
)
Runtimes
Every type and function have a runtime associated to it. This runtime describes where the types are physically located. It can be another API, a database, or any other services the typegate can connect to. The typegates uses that information to optimize the execution of the queries and minimize the amount of data moved.
In practice, function types are often not explicitly used and the usage of runtime sugar syntax is preferred.
http = HttpRuntime("https://random.org/api")
# same func as above
http.get(
"/flip_coin", t.struct({}), t.enum(["head", "tail"])
) # implicitly attaches runtime to all types
Analogy in SQL: a runtime is similar to a database instance running some requests.
Policies
Policies are a special type of function t.func(t.struct({...}), t.boolean().optional())
attachable to any other type. They are evaluated once per request and determine whether one of the polices authorizes the access or not. They receive the request context (see typegate) as argument allowing you to implement authorization, access control, or any other business logic.
The policy decision can be:
true
: the access is authorizedfalse
: the access is deniednull
: the access in inherited from the parent types
deno = DenoRuntime()
public = deno.policy("public", "() => true") # noqa
team_only = deno.policy("team", "(ctx) => ctx.user.role === 'admin'") # noqa
Analogy in SQL: policies are similar to Row Security Policies (RSP) or Row Level Security (RLS) concepts.
Triggers
Triggers are events launching the execution of one or multiple functions. They fire when a GraphQL request is received for the specific typegraph.
@typegraph(
)
def triggers(g: Graph):
# ...
g.expose(
public,
flip=http.get("/flip_coin", t.struct({}), t.enum(["head", "tail"])),
)
Analogy in SQL: a trigger is similar to receiving a new query.