Your first domain model
Start with templates/vernac-basic, or create a project using the Vernac IntelliJ
project wizard. The wizard sets Maven coordinates, the namespace and source paths
for you. Use JDK 25 for the build; the application targets Java 21.
A released 0.5.0 wizard selects matching release artifacts. A repository copy of the template uses 0.5.0-SNAPSHOT: install matching artifacts locally first. Released compiler and runtime artifacts are downloaded from Maven Central.
Build and explore
From the generated project directory, run ./mvnw clean test on Linux/macOS or
.\mvnw.cmd clean test in Windows PowerShell. In an IntelliJ Maven run
configuration, enter clean test.
Open src/main/vernac/org/example/tasks/tasks.vernac (with the corresponding
namespace path if you chose another namespace):
namespace org.example.tasks;
id TaskId;
value Title(String) validates {
require(!self.string().isBlank(), "Task title must not be blank");
};
value Status = PENDING | IN_PROGRESS | COMPLETED | CANCELLED;
aggregate Task[TaskId](Title title, mut Status) behavior {
modify void complete() {
self.status(Status.COMPLETED);
}
};
Title validates its value when created. Task.create(title, status) creates a
fresh identity. Only the status is mutable, and the behavior accesses it through
self. Generated classes live in org.example.tasks.domain.
The Java test in src/test/java/org/example/tasks/TaskTest.java creates a task,
completes it and checks the result. Read the generated Java under
target/generated-sources/vernac as well; edit the Vernac source, never the
generated classes.
Add your own behavior
Add this method inside the existing behavior block:
modify void start() {
self.status(Status.IN_PROGRESS);
}
Extend the Java test to call task.start() and assert Status.IN_PROGRESS
before completing the task. Rebuild with clean test.
Next, try Title.of(" ") in a test. Assert that it throws
org.vernac.runtime.DomainValidationException. Think about whether completing an
already cancelled task should be allowed: the initial example deliberately has
no transition policy. Add a policy and tests as a further exercise.
No database, Docker container or running Spring application is needed here. For persistence and migrations, continue with the tutorials linked from the tutorial index.
Next, follow a delivery operation through the architecture. The Task exercise taught the domain API; the next chapter connects it to transactions, persistence, events and an external capability.