Event Modeling in HCL specs
Announcing a .em.hcl language spec for Event Modeling.
It lets people document their event models as code.
Yes, there are currently projects like:
- yaml:
- json:
So you might ask why HCL?
I see it this way:
For this project, the block form is useful because the source resembles the concepts being discussed.
My personal motivation was:
- I want a schema in a language I can easily edit by hand;
- sorry to Mermaid diagrams, but they are NOT human-friendly for event modeling
- The JSON spec is also not something I am willing to write by hand
- I want to store it in a git repo
- And I want it to be rendered as a nice HTML event model that can live on GitLab/GitHub Pages as my system documentation.
Hence, I started this project. Writing Terraform files (HashiCorp created the HCL language) is pretty nice: it provides validation, rules, and syntax highlighting. So I thought:
- (It’s not yet searching for John Connor) I could ask an AI to vibe-slop me the project based on my inputs.
- I can use it in my own personal projects as the source of truth: Spec-Driven
Development, done as a spec, not Markdown (sorry,
.mdis not aspec). SysML is a spec,.feature(Gherkin) files are a spec,.hcl,.json,.yamlare specs, but not.mdfiles.
Here’s a simple example from the official eventmodeling.org site:

# Appointment scheduling with state change, state view, automation, and translation workflows.
actor "scheduler" {
title = "Scheduler"
auth_required = true
}
actor "calendar_user" {
title = "Calendar user"
auth_required = true
}
system "weather_provider" {
title = "Weather provider"
external = true
}
bounded_context "appointments" {
title = "Appointments"
aggregate "appointment" {
}
field_type "appointment_id" {
type = "UUID"
id_attribute = true
}
field_type "starts_at" {
type = "DateTime"
}
event "appointment_added" {
title = "Appointment Added"
aggregate = aggregate.appointment
field "appointment_id" {
type = field_type.appointment_id
}
field "starts_at" {
type = field_type.starts_at
}
}
}
bounded_context "weather" {
title = "Weather"
aggregate "weather_forecast" {
}
field_type "forecast" {
type = "String"
}
field_type "changed_at" {
type = "DateTime"
}
event "weather_predicted_for_appointment" {
title = "Weather predicted for appointment"
aggregate = aggregate.weather_forecast
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.forecast
}
}
event "updated_weather_prediction" {
title = "Updated Weather Prediction"
aggregate = aggregate.weather_forecast
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.forecast
}
}
}
bounded_context "weather_provider" {
title = "Weather Provider"
owner = system.weather_provider
external = true
event "weather_forecast_changed" {
title = "Weather Forecast Changed"
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
field "changed_at" {
type = field_type.weather.changed_at
}
}
}
state_change "schedule_appointment" {
title = "Schedule Appointment"
screen "schedule_appointment_ui" {
title = "UI"
actor = actor.scheduler
to = [command.add_appointment]
}
screen "post_appointment" {
title = "POST /appointment"
to = [command.add_appointment]
}
command "add_appointment" {
title = "Add Appointment"
aggregate = aggregate.appointments.appointment
to = [event.appointments.appointment_added]
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
scenario "appointment_is_scheduled" {
title = "Schedule an appointment"
when {
title = "Add Appointment"
command = command.add_appointment
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
then {
title = "Appointment Added"
event = event.appointments.appointment_added
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
}
}
state_view "view_calendar" {
title = "View Calendar"
readmodel "calendar" {
title = "Calendar"
question = "Which appointments are on the calendar?"
from = [event.appointments.appointment_added]
to = [screen.calendar_ui, screen.get_calendar]
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
screen "calendar_ui" {
title = "UI"
actor = actor.calendar_user
}
screen "get_calendar" {
title = "GET /calendar"
}
scenario "calendar_shows_scheduled_appointment" {
title = "Show a scheduled appointment on the calendar"
given {
title = "Appointment Added"
event = event.appointments.appointment_added
field "appointment_id" {
type = field_type.appointments.appointment_id
}
}
then {
title = "Calendar"
readmodel = readmodel.calendar
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
}
}
automation "add_weather_forecast" {
title = "Add Weather Forecast"
readmodel "appointments_without_weather_forecast" {
title = "Appointments without weather forecast"
question = "Which appointments do not have a weather forecast?"
from = [event.appointments.appointment_added]
to = [processor.weather_processor]
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
processor "weather_processor" {
title = "Weather Processor"
to = [command.add_weather_forecast_command]
}
command "add_weather_forecast_command" {
title = "Add Weather Forecast"
aggregate = aggregate.weather.weather_forecast
to = [event.weather.weather_predicted_for_appointment]
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
}
scenario "forecast_is_added_for_new_appointment" {
title = "Add a weather forecast for a scheduled appointment"
given {
title = "Appointment Added"
event = event.appointments.appointment_added
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "starts_at" {
type = field_type.appointments.starts_at
}
}
given {
title = "Appointments without weather forecast"
readmodel = readmodel.appointments_without_weather_forecast
}
when {
title = "Weather Processor"
processor = processor.weather_processor
}
then {
title = "Weather predicted for appointment"
event = event.weather.weather_predicted_for_appointment
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
}
}
}
translation "translate_weather_change" {
title = "Translate Changed Weather"
readmodel "changed_predictions" {
title = "Changed Predictions"
question = "Which external weather predictions changed?"
from = [event.weather_provider.weather_forecast_changed]
to = [processor.translator]
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
}
processor "translator" {
title = "Translator"
to = [command.translate_changed_weather]
}
command "translate_changed_weather" {
title = "Translate Changed Weather"
aggregate = aggregate.weather.weather_forecast
to = [event.weather.updated_weather_prediction]
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
}
scenario "external_weather_change_is_translated" {
title = "Translate a changed external weather forecast"
given {
title = "Weather Forecast Changed"
event = event.weather_provider.weather_forecast_changed
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
}
given {
title = "Changed Predictions"
readmodel = readmodel.changed_predictions
}
when {
title = "Translator"
processor = processor.translator
}
then {
title = "Updated Weather Prediction"
event = event.weather.updated_weather_prediction
field "appointment_id" {
type = field_type.appointments.appointment_id
}
field "forecast" {
type = field_type.weather.forecast
}
}
}
}
Other examples can be found here.
TL;DR:
If you’re a software engineer who uses a strongly statically typed programming language,
can rely on a compiler to argue with an AI, and prefers explicit domain models (DDD)
and specs (BDD), then please try Event Modeling with the .em.hcl language.
Project page is here.