Skip to content

Commit

Permalink
Add docs explaining generated code and its API
Browse files Browse the repository at this point in the history
Motivation:

Users should be able to understand how the generated code is structured
and how it may change over time.

Modifications:

Add two docs:
1. Explain the structure of the generate code and how to navigate it.
2. Explain how the generated code may change and hot to ensure it
   doesn't cause API breakages.

Result:

Better docs
  • Loading branch information
glbrntt committed Jan 28, 2025
1 parent 4bb3bea commit 2691277
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# API stability of generated code

Understand the impact of changes you make to your Protocol Buffers files on the
generated Swift code.

## Overview

The API of the generated code depends on three factors:

- The contents of the source `.proto` file.
- The options you use when generating the code.
- The code generator (the `protoc-gen-grpc-swift` plugin for `protoc`).

While this document applies specifically to the gRPC code generated and *not*
code for messages used as inputs and outputs of each method, the concepts still
broadly apply.

Some of the concepts used in this document are described in more detail in
<doc:Understanding-the-generated-code>.

## The source .proto file

The source `.proto` file defines the API of your service. You'll likely provide
it to users so that they can generate clients from it. In order to maintain API
stability for your service and for clients you must adhere to the following
rules:

1. You mustn't change the `package` the service is defined in.
2. You mustn't change or add the `swift_prefix` option.
3. You mustn't remove or change the name of any services in your `.proto` file.
4. You mustn't remove or change the name of any RPCs in your `.proto` file.
5. You mustn't change the message types used by any RPCs in your `.proto` file.

Failure to follow these will result in changes in the generated code which can
result in build failures for users.

Whilst this sounds restrictive you may do the following:

1. You may add a new RPC to an existing service in your `.proto` file.
2. You may add a new service to your `.proto` file (however it is recommended
that you define a single service per `.proto` file).

## The options you use for generating code

Code you generate into your Swift Package becomes part of the API of your
package. You must therefore ensure that downstream users of your package aren't
impacted by the options you use when generating code.

By default code is generated at the `internal` access level and therefore not
part of the public API. You must explicitly opt in to generating code at the
`public` access level. If you do this then you must be aware that changing what
is generated (clients, servers) affects the public API, as does the access level
of the generated code.

If you need to validate whether your API has changed you can use tools like
Swift Package Manager's API breakage diagnostic (`swift package
diagnose-api-breaking-changes`.) In general you should prefer providing users
with the service's `.proto` file so that they can generate clients, or provide a
library which wraps the client to hide the API of the generated code.

## The code generator

The gRPC Swift maintainers may need to evolve the generated code over time. This
will be done in a source-compatible way.

If APIs are no longer suitable then they may be deprecated in favour of new
ones. Existing API will never be removed and deprecated APIs will continue to
function.

If the generator introduces new ways to generate code which are incompatible
with the previously generated code then they will require explicit opt-in via an
option.

As gRPC Swift is developed the generated code may need to rely on newer
functionality from its runtime counterparts (`GRPCCore` and `GRPCProtobuf`).
This means that you should use the versions of `protoc-gen-grpc-swift` and
`protoc-gen-swift` resolved with your package rather than getting them from an
out-of-band (such as `homebrew`).
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Understanding the generated code

Understand what code is generated by `protoc-gen-grpc-swift` from a `.proto`
file and how to use it.

## Overview

The gRPC Swift Protobuf package provides a plugin to the Protocol Buffers
Compiler (`protoc`) called `protoc-gen-grpc-swift`. The plugin is responsible
for generating the gRPC specific code for services defined in a `.proto` file.

### Package namespace

Most `.proto` files contain a `package` directive near the start of the file
describing the namespace it belongs to. Here's an example:

```proto
package foo.bar.v1;
```

The package name "foo.bar.v1" is important as it is used as a prefix for
generated types. The default behaviour is to replace periods with underscores
and to capitalize each word and add a trailing underscore. For this package the
prefix is "Foo\_Bar\_V1\_". If you don't declare a package then the prefix will be
the empty string.

You can override the prefix by setting the `swift_prefix` option:

```proto
option swift_prefix = "FooBarV1";
package foo.bar.v1;
```

The prefix for types in this file would be "FooBarV1" instead of "Foo\_Bar\_V1\_".

### Service namespace

For each service declared in your `.proto` file, gRPC will generate a caseless
`enum` which is a namespace holding the generated protocols and types. The name
of this `enum` is `{PREFIX}{SERVICE}` where `{PREFIX}` is as described in the
previous section and `{SERVICE}` is the name of the service as declared in the
`.proto` file.

As an example the following definition creates a service namespace `enum` called
`Foo_Bar_V1_BazService` (the `{PREFIX}` is "Foo_Bar_V1_" and `{SERVICE}` is
"BazService"):

```proto
package foo.bar.v1;
service BazService {
// ...
}
```

Code generated for each service falls into three categories:

1. Service metadata,
2. Service code, and
3. Client code.

#### Service metadata

gRPC generates metadata for each service including a descriptor identifying the
fully qualified name of the service and information about each method in the
service. You likely won't need to interact directly with this information but
it's available should you need to.

#### Service code

Within a service namespace gRPC generates three service protocols:

1. `StreamingServiceProtocol`,
2. `ServiceProtocol`, and
3. `SimpleServiceProtocol`.

The full name of each protocol includes the service namespace.

> Example:
>
> For the `BazService` in the `foo.bar.v1` package the protocols would be:
>
> - `Foo_Bar_V1_BazService.StreamingServiceProtocol`,
> - `Foo_Bar_V1_BazService.ServiceProtocol`, and
> - `Foo_Bar_V1_BazService.SimpleServiceProtocol`.
Each of these protocols mirror the `service` defined in your `.proto` file with
one requirement per RPC. To implement your service you must implement one of
these protocols.

The protocols form a hierarchy with `StreamingServiceProtocol` at the bottom and
`SimpleServiceProtocol` at the top. `ServiceProtocol` refines
`StreamingServiceProtocol`, and `SimpleServiceProtocol` refines
`ServiceProtocol` (and `StreamingServiceProtocol` in turn).

The `StreamingServiceProtocol` implements each RPC as if it were a bidirectional
streaming RPC. This gives you the most flexibility at the cost of a harder to
implement API. It also puts the responsibility on you to ensure that each RPC
sends and receives the correct number of messages.

The `ServiceProtocol` enforces that the correct number of messages are sent and
received via its API. It also allows you to read request metadata and send both
initial and trailing metadata. The request and response types for these
requirements are in terms of `ServerRequest` and `ServerResponse`.

The `SimpleServiceProtocol` also enforces the correct number of messages are
sent and received via its API. However, it isn't defined in terms of
`ServerRequest` or `ServerResponse` so it doesn't allow you access metadata.
This limitation allows it to have the simplest API and is preferred if you don't
need access to metadata.

| Protocol | Enforces number of messages | Access to metadata
|----------------------------|-----------------------------|-------------------
| `StreamingServiceProtocol` | ✗ | ✓
| `ServiceProtocol` | ✓ | ✓
| `SimpleServiceProtocol` | ✓ | ✗

#### Client code

gRPC generates two types for the client within a service namespace:

1. `ClientProtocol`, and
2. `Client`.

Like the service code, the full name includes the namespace.

> Example:
>
> For the `BazService` in the `foo.bar.v1` package the client types would be:
>
> - `Foo_Bar_V1_BazService.ClientProtocol`, and
> - `Foo_Bar_V1_BazService.Client`.
The `ClientProtocol` defines one requirement for each RPC in terms of
`ClientRequest` and `ClientResponse`. You don't need to implement the protocol
as `Client` provides a concrete implementation.

gRPC also generates extensions on `ClientProtocol` to provide more ergonomic
APIs. These include versions which provide default arguments for various
parameters (like the message serializer and deserializers; call options and
response handler) and versions which don't use `ClientRequest` and
`ClientResponse` directly.
2 changes: 2 additions & 0 deletions Sources/GRPCProtobuf/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ This package provides three products:

- <doc:Installing-protoc>
- <doc:Generating-stubs>
- <doc:API-stability-of-generated-code>
- <doc:Understanding-the-generated-code>

### Serialization

Expand Down

0 comments on commit 2691277

Please sign in to comment.