Introduction
Specs, short for Specification(s), are a 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.