A simple example of server application in Spine to get you started.
- Install JDK 8 or higher.
- Clone the source code:
git clone git@github.com:spine-examples/server-quickstart.git- Run
./gradlew clean build(orgradlew.bat clean buildon Windows) in the project root folder.
The project consists of three modules.
Defines the Ubiquitous Language
of the application in Protobuf.
The model/src/main/proto directory contains the Protobuf definitions of the domain model:
Taskis an aggregate state type; as any entity type, it is marked with the(entity)option;TaskCreatedinevents.protois an event of theTaskAggregate;CreateTaskincommands.protois a command handled by theTaskAggregate;- the model may also contain other message types, e.g. identifiers (see
identifiers.proto), value types, etc.
-
Describes the business rules for Spine entities, such as Aggregates, in Java. See the
TaskAggregatewhich handles theCreateTaskcommand and applies the producedTaskCreatedevent. -
Plugs the
modelinto the infrastructure:- configures the storage;
- creates a
BoundedContextand registers repositories; - exposes the
BoundedContextinstance to the outer world through a set of gRPC services, provided by the framework.
See io.spine.tasks.server.ServerApp for implementation.
Run ServerApp.main() to start the server.
Interacts with the gRPC services, exposed by the server module:
- sends commands via
CommandServicestub; - sends queries via
QueryServicestub.
See io.spine.tasks.client.ClientApp for implementation.
Run ClientApp.main() to start the client and see it connecting to the server.
- Experiment with the model. Create a new command type in
commands.proto
import "spine/time/time.proto";
import "spine/time_options.proto";
// ...
message AssignDueDate {
TaskId task = 1;
spine.time.LocalDate due_date = 2 [(validate) = true, (required) = true, (when).in = FUTURE];
}Remember to import LocalDate via import "spine/time/time.proto";. This type is provided by
the Spine Time library. You don't have to perform any
additional steps to use it in your domain.
- Create a new event type in
events.proto:
import "spine/time/time.proto";
// ...
message DueDateAssigned {
TaskId task = 1;
spine.time.LocalDate due_date = 2 [(validate) = true, (required) = true];
}- Adjust the aggregate state:
import "spine/time/time.proto";
// ...
message Task {
option (entity) = {kind: AGGREGATE visibility: FULL};
// An ID of the task.
TaskId id = 1;
// A title of the task.
string title = 2 [(required) = true];
// The date and time by which this task should be completed.
spine.time.LocalDate due_date = 3 [(validate) = true, (required) = false];
}Make sure to run a Gradle build after the changing the Protobuf definitions:
./gradlew clean buildor for Windows:
gradlew.bat clean build
- Handle the
AssignDueDatecommand in theTaskAggregate:
@Assign
DueDateAssigned handle(AssignDueDate command) {
return DueDateAssigned
.newBuilder()
.setTask(command.getTask())
.setDueDate(command.getDueDate())
.vBuild();
}- Apply the emitted event:
@Apply
private void on(DueDateAssigned event) {
builder().setDueDate(event.getDueDate());
}- In
ClientApp, extend themain()method. Post another command:
AssignDueDate dueDateCommand = AssignDueDate
.newBuilder()
.setTask(taskId)
.setDueDate(LocalDates.of(2038, JANUARY, 19))
.vBuild();
commandService.post(requestFactory.command()
.create(dueDateCommand));and log the updated state:
QueryResponse updatedStateResponse = queryService.read(taskQuery);
info("The second response received: %s", Stringifiers.toString(updatedStateResponse));- Restart the server. Run the client and make sure that the due date is set to the task.