Skip to content
danthonywalker edited this page Feb 6, 2019 · 3 revisions

Introduction

Specs, short for Specification(s), are an unique design philosophy adopted by Discord4J to handle requests that contain multiple optional properties. They are very similar to the common builder pattern, but with two very important differentiating characteristics:

  1. The end-user does not construct the builder.
  2. The end-user does not construct the finalized object.

These two characteristics provide Discord4J with tremendous flexibility when it comes to constructing requests without breaking the API at a future date. Different requests to Discord may require different procedures and Specs allows Discord4J to "construct" these requests in an implementation-dependent manner while still providing the end-user control in "building" the request parameters using a singular syntax that is consistent across the API.

Example

All Specs that an end-user interacts with will be provided via a Consumer. For example, for MessageChannel#createMessage:

messageChannel.createMessage(spec -> /* manipulate the spec */)

One may note that all Spec instances have an asRequest method. This method is an internal behaviorally implementation-specific method and should never be called by the end-user. Once the Spec has been "built", simply leave it alone.

Mono<Message> message = messageChannel.createMessage(messageSpec -> {
    messageSpec.setContent("Content not in an embed!");
    // You can see in this example even with simple singular property defining specs the syntax is concise
    messageSpec.setEmbed(embedSpec -> embedSpec.setDescription("Description is in an embed!"));
});

Templates

It is a very common pattern, especially when dealing with embedded messages, to provide a "template" that can later be edited to fit a specific use-case. Using Consumer#andThen allows this pattern to be implemented easily:

Consumer<EmbedCreateSpec> template = spec -> {
    // Edit the spec as you normally would
};
...
// embedSpec can be edited as you normally would, but the edits from template will already be applied
Mono<Message> message = messageChannel.createMessage(messageSpec -> messageSpec.setEmbed(template.andThen(embedSpec -> {})));

This pattern additionally helps protect the end-user from accidentally sharing specs across multiple invocations, as the state is never "reset" and mutating Spec instances is not thread-safe.