docs: format with prettier (#1218)

This commit is contained in:
Patrik Oldsberg
2020-06-09 19:50:41 +02:00
committed by GitHub
parent 4f3375073d
commit a633dce818
20 changed files with 756 additions and 364 deletions
@@ -6,22 +6,22 @@
## Background
Backstage comes with a software catalog functionality, that you can use to
track all your software components and more. It can be powered by data from
various sources, and one of them that is included with the package, is a
custom database backed catalog. It has the ability to keep itself updated
automatically based on the contents of little descriptor files in your
version control system of choice. Developers create these files and maintain
them side by side with their code, and the catalog system reacts accordingly.
Backstage comes with a software catalog functionality, that you can use to track
all your software components and more. It can be powered by data from various
sources, and one of them that is included with the package, is a custom database
backed catalog. It has the ability to keep itself updated automatically based on
the contents of little descriptor files in your version control system of
choice. Developers create these files and maintain them side by side with their
code, and the catalog system reacts accordingly.
This ADR describes the default format of these descriptor files.
### Inspiration
Internally at Spotify, a home grown software catalog system is used heavily
and forms a core part of Backstage and other important pieces of the
infrastructure. The user experience, learnings and certain pieces of metadata
from that catalog are being carried over to the open source effort.
Internally at Spotify, a home grown software catalog system is used heavily and
forms a core part of Backstage and other important pieces of the infrastructure.
The user experience, learnings and certain pieces of metadata from that catalog
are being carried over to the open source effort.
The file format described herein, also draws heavy inspiration from the
[kubernetes object format](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/).
@@ -35,9 +35,9 @@ inside Backstage, or by push events from a CI/CD pipelines, or by webhook
triggers from the version control system, etc.
Each file describes one or more entities in accordance with the
[Backstage System Model](https://github.com/spotify/backstage/issues/390).
All of these entities have a common stucture and nomenclature, and they are
stored in the software catalog from which they then can be queried.
[Backstage System Model](https://github.com/spotify/backstage/issues/390). All
of these entities have a common stucture and nomenclature, and they are stored
in the software catalog from which they then can be queried.
Entities have distinct names, and they may reference each other by those names.
@@ -70,9 +70,9 @@ spec:
```
The root fields `apiVersion`, `kind`, `metadata`, and `spec` are part of the
_envelope_, defining the overall structure of all kinds of entity. Likewise,
the `name`, `namespace`, `labels`, and `annotations` metadata fields are of
special significance and have reserved purposes and distinct shapes.
_envelope_, defining the overall structure of all kinds of entity. Likewise, the
`name`, `namespace`, `labels`, and `annotations` metadata fields are of special
significance and have reserved purposes and distinct shapes.
See below for details about these fields.
@@ -88,30 +88,31 @@ first versions of the catalog will focus on the `Component` kind.
The `apiVersion`is the version of specification format for that particular
entity that this file is written against. The version is used for being able to
evolve the format, and the tuple of `apiVersion` and `kind` should be enough
for a parser to know how to interpret the contents of the rest of the document.
evolve the format, and the tuple of `apiVersion` and `kind` should be enough for
a parser to know how to interpret the contents of the rest of the document.
Backstage specific entities have an `apiVersion` that is prefixed with
`backstage.io/`, to distinguish them from other types of object that share
the same type of structure. This may be relevant when co-hosting these
`backstage.io/`, to distinguish them from other types of object that share the
same type of structure. This may be relevant when co-hosting these
specifications with e.g. kubernetes object manifests.
Early versions of the catalog will be using beta versions, e.g. `backstage.io/v1beta1`,
to signal that the format may still change. After that, we will be using
`backstage.io/v1` and up.
Early versions of the catalog will be using beta versions, e.g.
`backstage.io/v1beta1`, to signal that the format may still change. After that,
we will be using `backstage.io/v1` and up.
### `metadata`
A structure that contains metadata about the entity, i.e. things that aren't directly
part of the entity specification itself. See below for more details about this structure.
A structure that contains metadata about the entity, i.e. things that aren't
directly part of the entity specification itself. See below for more details
about this structure.
### `spec`
The actual specification data that describes the entity.
The precise structure of the `spec` depends on the `apiVersion` and `kind` combination,
and some kinds may not even have a `spec` at all. See further down in this document for
the specification structure of specific kinds.
The precise structure of the `spec` depends on the `apiVersion` and `kind`
combination, and some kinds may not even have a `spec` at all. See further down
in this document for the specification structure of specific kinds.
## Metadata
@@ -119,27 +120,31 @@ The `metadata` root field has the following nested structure.
### `name`
The name of the entity. This name is both meant for human eyes to recognize the entity,
and for machines and other components to reference the entity (e.g. in URLs or from
other entity specification files).
The name of the entity. This name is both meant for human eyes to recognize the
entity, and for machines and other components to reference the entity (e.g. in
URLs or from other entity specification files).
Names must be unique per kind, within a given namespace (if specified), at any point in
time. Names may be reused at a later time, after an entity is deleted from the registry.
Names must be unique per kind, within a given namespace (if specified), at any
point in time. Names may be reused at a later time, after an entity is deleted
from the registry.
Names are required to follow a certain format. Entities that do not follow those rules
will not be accepted for registration in the catalog. The ruleset is configurable to fit
your organization's needs, but the default behavior is as follows.
Names are required to follow a certain format. Entities that do not follow those
rules will not be accepted for registration in the catalog. The ruleset is
configurable to fit your organization's needs, but the default behavior is as
follows.
- Strings of length at least 1, and at most 63
- Must consist of sequences of `[a-z0-9A-Z]` possibly separated by one of `[-_.]`
- Must consist of sequences of `[a-z0-9A-Z]` possibly separated by one of
`[-_.]`
Example: `visits-tracking-service`, `CircleciBuildsDs_avro_gcs`
In addition to this, names are passed through a normalization function and then compared
to the same normalized form of other entity names and made sure to not collide. This rule
of uniqueness exists to avoid situations where e.g. both `my-component` and `MyComponent`
are registered side by side, which leads to confusion and risk. The normalization function
is also configurable, but the default behavior is as follows.
In addition to this, names are passed through a normalization function and then
compared to the same normalized form of other entity names and made sure to not
collide. This rule of uniqueness exists to avoid situations where e.g. both
`my-component` and `MyComponent` are registered side by side, which leads to
confusion and risk. The normalization function is also configurable, but the
default behavior is as follows.
- Strip out all characters outside of the set `[a-zA-Z0-9]`
- Convert to lowercase
@@ -148,55 +153,59 @@ Example: `CircleciBuildsDs_avro_gcs` -> `circlecibuildsdsavrogcs`
### `namespace`
The `name` of a namespace that the entity belongs to. This field is optional, and currently
has no special semantics apart from bounding the name uniqueness constraint if specified.
It is reserved for future use and may get broader semantic implication.
The `name` of a namespace that the entity belongs to. This field is optional,
and currently has no special semantics apart from bounding the name uniqueness
constraint if specified. It is reserved for future use and may get broader
semantic implication.
Namespaces may also be part of the catalog, and are `v1` / `Namespace` entities,
i.e. not Backstage specific but the same as in Kubernetes.
### `description`
A human readable description of the entity, to be shown in Backstage. Should be kept short
and informative, suitable to give an overview of the entity's purpose at a glance. More
detailed explanations and documentation should be placed elsewhere.
A human readable description of the entity, to be shown in Backstage. Should be
kept short and informative, suitable to give an overview of the entity's purpose
at a glance. More detailed explanations and documentation should be placed
elsewhere.
### `labels`
Labels are optional key/value pairs of that are attached to the entity, and their use is
identical to [kubernetes object labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/).
Labels are optional key/value pairs of that are attached to the entity, and
their use is identical to
[kubernetes object labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/).
Their main purpose is for references to other entities, and for information that is
in one way or another classifying for the current entity. They are often used as values
in queries or filters.
Their main purpose is for references to other entities, and for information that
is in one way or another classifying for the current entity. They are often used
as values in queries or filters.
Both the key and the value are strings, subject to the following restrictions.
Keys have an optional prefix followed by a slash, and then the name part which is required.
The prefix must be a valid lowercase domain name, at most 253 characters in total. The name
part must be sequences of `[a-zA-Z0-9]` separated by any of `[-_.]`, at most 63 characters
in total.
Keys have an optional prefix followed by a slash, and then the name part which
is required. The prefix must be a valid lowercase domain name, at most 253
characters in total. The name part must be sequences of `[a-zA-Z0-9]` separated
by any of `[-_.]`, at most 63 characters in total.
The `backstage.io/` prefix is reserved for use by Backstage core components. Some keys such as
`system` also have predefined semantics.
The `backstage.io/` prefix is reserved for use by Backstage core components.
Some keys such as `system` also have predefined semantics.
Values are strings that follow the same restrictions as `name` above.
### `annotations`
An object with arbitrary non-identifying metadata attached to the entity,
identical in use to [kubernetes object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/).
identical in use to
[kubernetes object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/).
Their purpose is mainly, but not limited, to reference into external systems. This could
for example be a reference to the git ref the entity was ingested from, to monitoring
and logging systems, to pagerduty schedules, etc.
Their purpose is mainly, but not limited, to reference into external systems.
This could for example be a reference to the git ref the entity was ingested
from, to monitoring and logging systems, to pagerduty schedules, etc.
Both the key and the value are strings, subject to the following restrictions.
Keys have an optional prefix followed by a slash, and then the name part which is required.
The prefix must be a valid lowercase domain name, at most 253 characters in total. The name
part must be sequences of `[a-zA-Z0-9]` separated by any of `[-_.]`, at most 63 characters
in total.
Keys have an optional prefix followed by a slash, and then the name part which
is required. The prefix must be a valid lowercase domain name, at most 253
characters in total. The name part must be sequences of `[a-zA-Z0-9]` separated
by any of `[-_.]`, at most 63 characters in total.
The `backstage.io/` prefix is reserved for use by Backstage core components.
@@ -6,30 +6,50 @@
## Context
When CommonJS was the primary authoring format, the best practice was to export only one thing from a module using the `module.exports = ...` format. This aligned with the [UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one thing well". The module would be consumed (`const localName = require('the-module');`) without having to know the internal structure.
When CommonJS was the primary authoring format, the best practice was to export
only one thing from a module using the `module.exports = ...` format. This
aligned with the
[UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one
thing well". The module would be consumed
(`const localName = require('the-module');`) without having to know the internal
structure.
Now, ESModules are the primary authoring format. They have numerous benefits, such as compile-time verification of exports, and standards-defined semantics. They have a similar mechanism known as "default exports", which allows for a consumer to `import localName from 'the-module';`. This is implicitly the same as `import { default as localName } from 'the-module';`.
Now, ESModules are the primary authoring format. They have numerous benefits,
such as compile-time verification of exports, and standards-defined semantics.
They have a similar mechanism known as "default exports", which allows for a
consumer to `import localName from 'the-module';`. This is implicitly the same
as `import { default as localName } from 'the-module';`.
However, there are numerous reasons to avoid default exports, as documented by others before:
However, there are numerous reasons to avoid default exports, as documented by
others before:
- https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/
A summary:
- They add indirection by encouraging a developer to create local names for modules, increasing cognitive load and slowing down code comprehension: `import TheListThing from 'not-a-list-thing';`.
- They thwart tools, such as IDEs, that can automatically rename and refactor code.
- They promote typos and mistakes, as the imported member is completely up to the consuming developer to define.
- They are ugly in CommonJS interop, as the default property must be manually specified by the consumer. This is often hidden by Babel's module interop.
- They break re-exports due to name conflicts, forcing the developer to manually name each.
- They add indirection by encouraging a developer to create local names for
modules, increasing cognitive load and slowing down code comprehension:
`import TheListThing from 'not-a-list-thing';`.
- They thwart tools, such as IDEs, that can automatically rename and refactor
code.
- They promote typos and mistakes, as the imported member is completely up to
the consuming developer to define.
- They are ugly in CommonJS interop, as the default property must be manually
specified by the consumer. This is often hidden by Babel's module interop.
- They break re-exports due to name conflicts, forcing the developer to manually
name each.
Using named exports helps prevent needing to rename symbols, which has myriad benefts. A few are:
Using named exports helps prevent needing to rename symbols, which has myriad
benefts. A few are:
- IDE tools like "Find All References" and "Go To Definition" function
- Manual codebase searching ("grep", etc) is easier with a unique symbol
## Decision
We will stop using default exports except when absolutely necessary (such as [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules). A workaround exists for those that would prefer to never use `default`:
We will stop using default exports except when absolutely necessary (such as
[`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules).
A workaround exists for those that would prefer to never use `default`:
```ts
const Component = React.lazy(() =>
@@ -39,6 +59,9 @@ const Component = React.lazy(() =>
## Consequences
We will actively work to remove them from our codebases, being as explicit as possible. Have a connected component? `export const ConnectedComponent = connect(Component)`.
We will actively work to remove them from our codebases, being as explicit as
possible. Have a connected component?
`export const ConnectedComponent = connect(Component)`.
We will add tools, such as lint rules, to help migrate away from default exports.
We will add tools, such as lint rules, to help migrate away from default
exports.
@@ -6,7 +6,8 @@
## Context
With a growing number of exports of packages like `@backstage/core`, it is becoming more and more difficult to answer questions such as
With a growing number of exports of packages like `@backstage/core`, it is
becoming more and more difficult to answer questions such as
> Is the export in this module also exported by the package?
@@ -14,12 +15,19 @@ or
> What is exported from this directory?
We currently do not use any pattern for how to structure exports. There is a mix of package-level re-exports deep into the directory tree, shallow re-exports for each directory, exports using `*` and explicit lists of each symbol, etc.
The mix and lack of predictability makes it difficult to reason about the boundaries of a module, and for example knowing whether is is safe to export a symbol in a given file.
We currently do not use any pattern for how to structure exports. There is a mix
of package-level re-exports deep into the directory tree, shallow re-exports for
each directory, exports using `*` and explicit lists of each symbol, etc. The
mix and lack of predictability makes it difficult to reason about the boundaries
of a module, and for example knowing whether is is safe to export a symbol in a
given file.
## Decision
We will make each exported symbol traceable through index files all the way down to the root of the package, `src/index.ts`. Each index file will only re-export from its own immediate directory children, and only index files will have re-exports. This gives a file tree similar to this:
We will make each exported symbol traceable through index files all the way down
to the root of the package, `src/index.ts`. Each index file will only re-export
from its own immediate directory children, and only index files will have
re-exports. This gives a file tree similar to this:
```text
index.ts
@@ -33,16 +41,25 @@ lib/index.ts
/helper.ts
```
To check whether for example `SubComponentY` is exported from the package, it should be possible to traverse the index files towards the root, starting at the adjacent one. If there is any index file that doesn't export the previous one, the symbol is not publicly exported. For example, if `components/ComponentX/index.ts` exports `SubComponentY`, but `components/index.ts` does not re-export `./ComponentX`, one should be certain that `SubComponentY` is not exported outside the package. This rule would be broken if for example the root `index.ts` re-exports `./components/ComponentX`
To check whether for example `SubComponentY` is exported from the package, it
should be possible to traverse the index files towards the root, starting at the
adjacent one. If there is any index file that doesn't export the previous one,
the symbol is not publicly exported. For example, if
`components/ComponentX/index.ts` exports `SubComponentY`, but
`components/index.ts` does not re-export `./ComponentX`, one should be certain
that `SubComponentY` is not exported outside the package. This rule would be
broken if for example the root `index.ts` re-exports `./components/ComponentX`
In addition, index files that are re-exporting other index files should always use wildcard form, that is:
In addition, index files that are re-exporting other index files should always
use wildcard form, that is:
```ts
// in components/index.ts
export * from './ComponentX';
```
Index files that are re-exporting symbols from non-index files should always enumerate all exports, that is:
Index files that are re-exporting symbols from non-index files should always
enumerate all exports, that is:
```ts
// in components/ComponentX/index.ts
@@ -50,14 +67,16 @@ export { ComponentX } from './ComponentX';
export type { ComponentXProps } from './ComponentX';
```
Internal cross-directory imports are allowed from non-index modules to index modules, for example:
Internal cross-directory imports are allowed from non-index modules to index
modules, for example:
```ts
// in components/ComponentX/ComponentX.tsx
import { UtilityX } from '../../lib/UtilityX';
```
Imports that bypass an index file are discouraged, but may sometimes be necessary, for example:
Imports that bypass an index file are discouraged, but may sometimes be
necessary, for example:
```ts
// in components/ComponentX/ComponentX.tsx
@@ -66,6 +85,9 @@ import { helperFunc } from '../../lib/UtilityX/helper';
## Consequences
We will actively work to rework the export structure in our codebase, prioritizing the library packages such as `@backstage/core` and `@backstage/backend-common`.
We will actively work to rework the export structure in our codebase,
prioritizing the library packages such as `@backstage/core` and
`@backstage/backend-common`.
If possible, we will add tools, such as lint rules, to help enforce the export structure.
If possible, we will add tools, such as lint rules, to help enforce the export
structure.
@@ -6,7 +6,8 @@
## Context
We want to standardize on a few core entities that we are tracking in the Backstage catalog. This allows us to build specific plugins around them.
We want to standardize on a few core entities that we are tracking in the
Backstage catalog. This allows us to build specific plugins around them.
## Decision
@@ -14,17 +15,26 @@ Backstage should eventually support the following core entities:
- **Components** are individual pieces of software
- **APIs** are the boundaries between different components
- **Resources** are physical or virtual infrastructure needed to operate a component
- **Resources** are physical or virtual infrastructure needed to operate a
component
![Catalog Core Entities](catalog-core-entities.png)
For now, we'll start by only implementing support for the Component entity in the Backstage catalog. This can later be extended to APIs, Resources and other potentially useful entities.
For now, we'll start by only implementing support for the Component entity in
the Backstage catalog. This can later be extended to APIs, Resources and other
potentially useful entities.
### Component
A component is a piece of software, for example a mobile application feature, web site, backend service or data pipeline (list not exhaustive). A component can be tracked in source control, or use some existing open source or commercial software. It can implement APIs for other components to consume. In turn it might depend on APIs implemented by other components, or resources that are attached to it at runtime.
A component is a piece of software, for example a mobile application feature,
web site, backend service or data pipeline (list not exhaustive). A component
can be tracked in source control, or use some existing open source or commercial
software. It can implement APIs for other components to consume. In turn it
might depend on APIs implemented by other components, or resources that are
attached to it at runtime.
Component entities are typically defined in YAML descriptor files next to the code of the component, and could look like this (actual schema will evolve):
Component entities are typically defined in YAML descriptor files next to the
code of the component, and could look like this (actual schema will evolve):
```yaml
apiVersion: backstage.io/v1beta1
@@ -37,11 +47,20 @@ spec:
### API
APIs form an abstraction that allows large software ecosystems to scale. Thus, APIs are a first class citizen in the Backstage model and the primary way to discover existing functionality in the ecosystem.
APIs form an abstraction that allows large software ecosystems to scale. Thus,
APIs are a first class citizen in the Backstage model and the primary way to
discover existing functionality in the ecosystem.
APIs are implemented by components and make their boundaries explicit. They might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g. framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs exposed by components need to be in a known machine-readable format so we can build further tooling and analysis on top.
APIs are implemented by components and make their boundaries explicit. They
might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data
schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g.
framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs
exposed by components need to be in a known machine-readable format so we can
build further tooling and analysis on top.
APIs are typically indexed from existing definitions in source control and thus wouldn't need their own descriptor files, but would be stored in the catalog somewhat like this (actual schema will evolve):
APIs are typically indexed from existing definitions in source control and thus
wouldn't need their own descriptor files, but would be stored in the catalog
somewhat like this (actual schema will evolve):
```yaml
apiVersion: backstage.io/v1beta1
@@ -54,9 +73,11 @@ spec:
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
@@ -64,9 +85,16 @@ spec:
### Resource
Resources are the infrastructure your software needs to operate at runtime like Bigtable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with components and APIs will allow us to visualize and create tooling around them in Backstage.
Resources are the infrastructure your software needs to operate at runtime like
Bigtable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together
with components and APIs will allow us to visualize and create tooling around
them in Backstage.
Resources are typically indexed from declarative definitions (e.g. Terraform, GCP Config Connector, AWS Cloud Formation) and/or inventories from cloud providers (e.g. GCP Asset Inventory) and thus wouldn't need their own descriptor files, but would be stored in the catalog somewhat like this (actual schema will evolve):
Resources are typically indexed from declarative definitions (e.g. Terraform,
GCP Config Connector, AWS Cloud Formation) and/or inventories from cloud
providers (e.g. GCP Asset Inventory) and thus wouldn't need their own descriptor
files, but would be stored in the catalog somewhat like this (actual schema will
evolve):
```yaml
apiVersion: backstage.io/v1beta1
@@ -80,4 +108,5 @@ spec:
## Consequences
We will continue fleshing out support for the Component entity in the Backstage catalog.
We will continue fleshing out support for the Component entity in the Backstage
catalog.