Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Serializer APIs, consolidation of ContentCodec, and gRPC MethodDescriptor #1673

Merged
merged 20 commits into from
Aug 7, 2021

Conversation

Scottmitch
Copy link
Member

Motivation:
Supporting a custom transport for gRPC currently requires
reflection and special knowledge of types for serialization. The
generated code has information related to types and serialization that
can be exposed to the runtime which also allows supporting custom
transports.

This task requires exposing serializer APIs into the public
API. Our existing serializer APIs were heavily influced by streaming use
cases and JSON serialization which can serialize any type and generally
doesn't have type restrictions. This lead to a general
SerializationProvider API which is impossible to constrain types due to
the generics being at the method level, which doesn't account for
implementing serialization for type constrained approached (protobuf
MessageLite). Also the serialization APIs couple scalar and streaming
use cases which in practice are more commonly decoupled into scalar
(individual objects) and streaming (which applies some framing around
individual objects). These concepts are coupled which leads to confusing
specializations (deserializeAggregatedSingle, deserializeAggregated)
which are easy to misuse and lead to invalid serialization in practice
(streaming {form url, string} encoding are not properly framed and may
not be deserialized correctly). The existing Serializer APIs also do not
promote composability in that if you can serialize one object you should
be able to re-use that serialization for a stream of objects with some
frameing applied on top (required for gRPC).

Modifications:

  • Introduce a new servicetalk-serializer-api package with new
    Serializer APIs.
  • Introduce a new servicetalk-serializer-utils package with common
    utilities for serialization such as streaming framing (fixed and
    varint length prefixed)
  • Introduce BufferEncoder APIs in servicetalk-encoding-api for
    compression and decompression. This also comes with
    NettyBufferEncoders and NettyCompression to create Netty backed
    implementations.
  • Introduce HttpStreamingSerializer and new HttpSerializer APIs in
    servicetalk-http-api, and modify all request/response APIs to use the
    new serializer APIs. Introduce new methods to deserialize and process
    trailers.
  • Introduce HttpSerializers in servicetalk-http-api for implementations
    of the new APIs.
  • Introduce JacksonSerializerCache in servicetalk-http-api as the new
    entry point to create/cache serializers for JSON.
  • Introduce ProtobufSerializerCache in servicetalk-data-protobuf as the
    new entry point to create/cache serializers for protobuf.
  • Introduce MethodDescriptor API into servicetalk-grpc-api and update
    code generation to use this new API.
  • Update all examples avoid usage of deprecated APIs and leverage new
    APIs.
  • Deprecate the following APIs and related methods in favor of the new
    types descrbied above:
    • SerializationProvider, JacksonSerializationProvider,
      ProtobufSerializationProvider, GrpcSerializationProvider,
      HttpSerializationProvider
    • ContentCodec, CodecDecodingException, CodecEncodingException,
      ContentCodings
    • GrpcMetadata#path(), code generated types that extend
      DefaultGrpcClientMetadata

Result:
gRPC API supports MethodDescriptor, which uses new Serializer APIs, and
the entire code base has been updated to use the new APIs consistently.

@Scottmitch
Copy link
Member Author

test failure attributed to #1579

@Scottmitch Scottmitch force-pushed the grpc_method_descriptor branch from a3dcaab to 79481dc Compare July 16, 2021 13:37
@Scottmitch Scottmitch requested a review from chemicL July 16, 2021 13:44
@Scottmitch
Copy link
Member Author

build failure attributed to #1663

@Scottmitch Scottmitch force-pushed the grpc_method_descriptor branch from a31c3a5 to 313c05d Compare July 16, 2021 16:48
@Scottmitch
Copy link
Member Author

build failure attributed to #1663

@bondolo bondolo added API PR with API changes (New or Deprecated) breaking-change PR with breaking changes, removed APIs or other binary incompatibilities. labels Jul 16, 2021
Copy link
Contributor

@bondolo bondolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

70/304 files so far…

@Scottmitch Scottmitch force-pushed the grpc_method_descriptor branch 2 times, most recently from 52cfb1f to f20e3c6 Compare July 17, 2021 00:03
@Scottmitch
Copy link
Member Author

another #1579

@Scottmitch
Copy link
Member Author

another #1663

1 similar comment
@Scottmitch
Copy link
Member Author

another #1663

@Scottmitch Scottmitch force-pushed the grpc_method_descriptor branch from 6dfba1d to 1b4c5e9 Compare July 20, 2021 22:01
@Scottmitch
Copy link
Member Author

another #1579 and #1663

@Scottmitch
Copy link
Member Author

#1507 and #1663

@Scottmitch Scottmitch force-pushed the grpc_method_descriptor branch 2 times, most recently from a164ae6 to b623507 Compare July 21, 2021 20:26
@Scottmitch
Copy link
Member Author

build failure attributed to #1579

Copy link
Contributor

@bondolo bondolo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approve for examples portion

@@ -26,8 +26,7 @@
import java.util.function.BooleanSupplier;

import static io.servicetalk.concurrent.api.Single.succeeded;
import static io.servicetalk.http.api.HttpSerializationProviders.textDeserializer;
import static io.servicetalk.http.api.HttpSerializationProviders.textSerializer;
import static io.servicetalk.http.api.HttpSerializers.textSerializerUtf8;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems odd to see textSerializerUtf8 in sites that are deserialization. Perhaps naming it textSerializationUtf8 would be less suggestive that the usage is serializing rather than deserializing.

* Append this filter before others that are expected to to see compressed content for this request/response, and after
* other filters that expect to see/manipulate the original payload.
*/
public final class ContentEncodingHttpServiceFilter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Coding as noun has both the meaning of encoding or decoding. I think the name before made more sense. The comment is worth considering across the whole encoding/decoding APIs (didn't want to duplicate it).

Copy link
Member Author

@Scottmitch Scottmitch Aug 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related/duplicate of #1673 (comment) ... lets discuss there if necessary.

Copy link
Contributor

@tkountis tkountis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st round.
General comments:

  • that I didn't want to repeat, it's probably worth sticking to ser/deser naming convention to all implementations.
  • new APIs are certainly better and I love the effort with varint streaming support.

}

@Nullable
static <T> T matchAndRemoveEncoding(final List<T> supportedEncoders,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyway we can extract parts of this to remove duplication between this and HeaderUtils#negotiateAcceptedEncodingRaw

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried sharing code between these two methods but they are doing different jobs and I couldn't find a good way. I considered at least moving them into the same HeaderUtils file, to keep similar code close but the matchAndRemoveEncoding can be kept package private if it stays here.

matchAndRemoveEncoding -> needs to remove the encoding header that is matched at preserve ordering (bcz the body has been decoded and the header no longer applies). first header must supported values or else we can't decode and its an error.
negotiateAcceptedEncodingRaw -> finds a matching encoding, so it can be applied (no removal of headers). any match is acceptable.


import static io.servicetalk.utils.internal.CharsetUtils.standardCharsets;

abstract class AbstractStringSerializer implements SerializerDeserializer<String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serializer/Deserializer in name? same for subclass

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A shorthand in the form of SerDe is frequently used in the community.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strategy I used was to be explicit with the interface names (SerializerDeserializer, BufferEncoderDecoder) but just use the "serializer" variant for concrete method/type names that fill both roles for brevity (StringSerializer, NettyBufferEncoders#bufferEncoder(..), HttpSerializers#textSerializerUtf8()). In practice I'm not sure lengthening the names of concrete types/methods, or using abbreviations (SerDe), will improve understandability/readability.

* Serializes and deserializes to/from JSON via jackson.
* @param <T> The type of objects to serialize.
*/
final class JacksonSerializer<T> implements SerializerDeserializer<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider Serialiazer/Deserializer in name

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate of #1673 (comment) ... lets discuss there if necessary.

};
}

private static final class BlockingStreamingHttpMessageBodyAdapter<I> implements BlockingIterator<Buffer> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should access to this class' state be made thread-safe? BlockingIterator comes from the io.servicetalk.concurrent package. I'd assume it can be used in a concurrent context. The parent class has no mention of it requiring single-thread access.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No this isn't necessary. BlockingIterator is in concurrent package because it is how we convert to/from java.util.Iterator related types. This type just adds timeouts at the call site and makes iterable Closable (which will cancel the underlying publisher if it isn't fully consumed).

@Scottmitch
Copy link
Member Author

build failure attributed to #1579

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will submit my review in chunks:

@Scottmitch
Copy link
Member Author

build failure attributed to #1579

@Scottmitch
Copy link
Member Author

another #1579

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far the approach looks good! Lots of improvements.
I still need to look at grpc. Everything else is reviewed. But don't be blocked by that.

…scriptor

Motivation:
Supporting a custom transport for gRPC currently requires
reflection and special knowledge of types for serialization. The
generated code has information related to types and serialization that
can be exposed to the runtime which also allows supporting custom
transports.

This task requires exposing serializer APIs into the public
API. Our existing serializer APIs were heavily influced by streaming use
cases and JSON serialization which can serialize any type and generally
doesn't have type restrictions. This lead to a general
SerializationProvider API which is impossible to constrain types due to
the generics being at the method level, which doesn't account for
implementing serialization for type constrained approached (protobuf
MessageLite). Also the serialization APIs couple scalar and streaming
use cases which in practice are more commonly decoupled into scalar
(individual objects) and streaming (which applies some framing around
individual objects). These concepts are coupled which leads to confusing
specializations (deserializeAggregatedSingle, deserializeAggregated)
which are easy to misuse and lead to invalid serialization in practice
(streaming {form url, string} encoding are not properly framed and may
not be deserialized correctly). The existing Serializer APIs also do not
promote composability in that if you can serialize one object you should
be able to re-use that serialization for a stream of objects with some
frameing applied on top (required for gRPC).

Modifications:
- Introduce a new servicetalk-serializer-api package with new
  Serializer APIs.
- Introduce a new servicetalk-serializer-utils package with common
  utilities for serialization such as streaming framing (fixed and
  varint length prefixed)
- Introduce BufferEncoder APIs in servicetalk-encoding-api for
  compression and decompression. This also comes with
  NettyBufferEncoders and NettyCompression to create Netty backed
  implementations.
- Introduce HttpStreamingSerializer and new HttpSerializer APIs in
  servicetalk-http-api, and modify all request/response APIs to use the
  new serializer APIs. Introduce new methods to deserialize and process
  trailers.
- Introduce HttpSerializers in servicetalk-http-api for implementations
  of the new APIs.
- Introduce JacksonSerializerCache in servicetalk-http-api as the new
  entry point to create/cache serializers for JSON.
- Introduce ProtobufSerializerCache in servicetalk-data-protobuf as the
  new entry point to create/cache serializers for protobuf.
- Introduce MethodDescriptor API into servicetalk-grpc-api and update
  code generation to use this new API.
- Update all examples avoid usage of deprecated APIs and leverage new
  APIs.
- Deprecate the following APIs and related methods in favor of the new
  types descrbied above:
  - SerializationProvider, JacksonSerializationProvider,
    ProtobufSerializationProvider, GrpcSerializationProvider,
    HttpSerializationProvider
  - ContentCodec, CodecDecodingException, CodecEncodingException,
    ContentCodings
  - GrpcMetadata#path(), code generated types that extend
    DefaultGrpcClientMetadata

Result:
gRPC API supports MethodDescriptor, which uses new Serializer APIs, and
the entire code base has been updated to use the new APIs consistently.

make control flow in ConnectionCloseHeaderHandlingTest and
GracefulConnectionClosureHandlingTest more robust
@Scottmitch Scottmitch force-pushed the grpc_method_descriptor branch from 9d8543d to b6761a7 Compare August 7, 2021 00:48
* Get the {@link HttpMessageBodyIterable} for this request.
* @return the {@link HttpMessageBodyIterable} for this request.
*/
HttpMessageBodyIterable<Buffer> messageBody();
Copy link
Member Author

@Scottmitch Scottmitch Aug 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation of #1673 (comment)

@idelpivnitskiy @chemicL - note that I removed BlockingStreamingHttpMessageBody originally introduced in this PR in favor of HttpMessageBodyIterable (happy to discuss/iterate). The motivation is the functional APIs such as TrailersTransformer doesn't lend themselves to the sequential programming style of traditional blocking APIs. I meant to deprecate the transform methods on the blocking request/response APIs but forgot (will push this change). The BlockingIterable types are still cumbersome to create but I think that can be improved over time (more utilities) and also we have a task to investigate removal of BlockingIterable (in favor of ClosableIterable).

@Scottmitch
Copy link
Member Author

build failure attributed to #1294

@Scottmitch
Copy link
Member Author

build failure attributed to #1507

@Scottmitch Scottmitch merged commit 4ce1aa6 into apple:main Aug 7, 2021
@Scottmitch Scottmitch deleted the grpc_method_descriptor branch August 7, 2021 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API PR with API changes (New or Deprecated) breaking-change PR with breaking changes, removed APIs or other binary incompatibilities.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants