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.protoimport "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
LocalDateviaimport "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 build
or 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.