← Blog

Model-Driven Development Tutorial

A complete tutorial on model-driven development (MDD). Learn how to use UML diagrams to generate working applications with less manual coding and fewer bugs.

Open Workbench Watch Demo Start Building Free

Model-Driven Development (MDD) is the practice of using models—not just code—as the primary artifact for building software. Instead of writing everything by hand, you design your system in diagrams and generate code from those models.

This tutorial covers the complete MDD workflow: from initial requirements to generated, runnable code.

What is Model-Driven Development?

In traditional development, you write code directly. Models (if they exist) are documentation—they describe what was built, not what should be built.

In MDD, the model is the source of truth:

Traditional:
  Requirements → Code → (maybe) Diagrams for docs

Model-Driven:
  Requirements → Models → Generated Code → Refinement

MDD vs. Low-Code vs. No-Code

ApproachPrimary ArtifactTarget UserFlexibility
TraditionalSource codeDevelopersMaximum
Model-DrivenUML/domain modelsDevelopers/ArchitectsHigh
Low-CodeVisual buildersCitizen developersMedium
No-CodeTemplates/configBusiness usersLimited

MDD sits between traditional coding and low-code: you still write code, but the generator handles the boilerplate.


Why Use Model-Driven Development?

1. Faster scaffolding

Instead of manually creating folder structures, interfaces, and basic implementations, generate them from your model.

2. Consistent architecture

The generator enforces patterns. Every entity follows the same structure. Every service has the same interface style.

3. Living documentation

Your diagrams are your code (or at least, they produce it). When the model changes, the code changes—no stale documentation.

4. Early validation

Catch design mistakes before writing code. Model validation finds issues like:

  • Circular dependencies
  • Missing relationships
  • Incomplete workflows

5. Reduced bugs

Less handwritten code means fewer places for typos, copy-paste errors, and inconsistencies.


The MDD Workflow: Step by Step

Step 1: Capture Requirements as a System Description

Before any diagrams, write a system description that captures:

  • Users and roles: Who uses the system?
  • Core entities: What are the main “things” in the system?
  • Key workflows: What are the 3-5 most important processes?
  • Integrations: What external systems does it connect to?
  • Constraints: Security, performance, compliance requirements?

Example: Task Management System

## Task Management System

### Users
- Team members: create and complete tasks
- Team leads: assign tasks, view team progress
- Admins: manage teams, configure settings

### Core Entities
- Task: has title, description, status, assignee, due date
- Project: groups tasks, has members and a lead
- Team: groups users, has projects
- User: has name, email, role

### Key Workflows
1. Create and assign a task
2. Move task through status lifecycle (todo → in progress → review → done)
3. Generate weekly progress report

### Integrations
- Slack notifications for assignments
- Calendar sync for due dates
- GitHub PR linking

### Constraints
- Tasks cannot be assigned to users outside the project
- Only team leads can create projects
- Completed tasks are archived after 30 days

Step 2: Create UML Class Diagrams

Transform your entities into a class diagram:

┌─────────────────────────┐       ┌─────────────────────────┐
│        <<entity>>       │       │        <<entity>>       │
│          Task           │       │         Project         │
├─────────────────────────┤       ├─────────────────────────┤
│ - id: TaskId            │       │ - id: ProjectId         │
│ - title: string         │  ◆────│ - name: string          │
│ - description: string   │       │ - leadId: UserId        │
│ - status: TaskStatus    │       │ - memberIds: UserId[]   │
│ - assigneeId: UserId    │       └─────────────────────────┘
│ - dueDate: Date         │                  │
│ - projectId: ProjectId  │                  │ belongs to
└─────────────────────────┘                  ▼
                                  ┌─────────────────────────┐
                                  │        <<entity>>       │
                                  │          Team           │
                                  ├─────────────────────────┤
                                  │ - id: TeamId            │
                                  │ - name: string          │
                                  │ - memberIds: UserId[]   │
                                  └─────────────────────────┘

                                             │ members

                                  ┌─────────────────────────┐
                                  │        <<entity>>       │
                                  │          User           │
                                  ├─────────────────────────┤
                                  │ - id: UserId            │
                                  │ - name: string          │
                                  │ - email: string         │
                                  │ - role: UserRole        │
                                  └─────────────────────────┘

Also model value objects and enums:

┌─────────────────────────┐       ┌─────────────────────────┐
│     <<enumeration>>     │       │     <<enumeration>>     │
│       TaskStatus        │       │        UserRole         │
├─────────────────────────┤       ├─────────────────────────┤
│ TODO                    │       │ MEMBER                  │
│ IN_PROGRESS             │       │ LEAD                    │
│ IN_REVIEW               │       │ ADMIN                   │
│ DONE                    │       └─────────────────────────┘
│ ARCHIVED                │
└─────────────────────────┘

Step 3: Model Behavior with Sequence Diagrams

For your key workflows, create sequence diagrams:

Workflow: Assign Task

TeamLead     AssignTaskUseCase    TaskRepository    NotificationService
    │               │                   │                   │
    │── assign(taskId, userId) ────────▶│                   │
    │               │── findById() ─────▶                   │
    │               │◀─── task ─────────│                   │
    │               │                   │                   │
    │               │── validate() ─────┤                   │
    │               │   (user in project?)                  │
    │               │                   │                   │
    │               │── task.assign() ──┤                   │
    │               │── save(task) ─────▶                   │
    │               │                   │                   │
    │               │── notify(userId, task) ──────────────▶│
    │               │                   │     (Slack msg)   │
    │◀─── Result ───│                   │                   │

Step 4: Model State with State Machine Diagrams

For stateful entities, create state machine diagrams:

                    ┌─────────┐
                    │  TODO   │
                    └────┬────┘
                         │ start()

                 ┌───────────────┐
                 │  IN_PROGRESS  │
                 └───────┬───────┘
                         │ submitForReview()

                  ┌─────────────┐
                  │  IN_REVIEW  │◄────┐
                  └──────┬──────┘     │
           approve()     │            │ requestChanges()
                         ▼            │
                    ┌─────────┐       │
                    │  DONE   │───────┘
                    └────┬────┘
                         │ archive() [after 30 days]

                   ┌──────────┐
                   │ ARCHIVED │
                   └──────────┘

Step 5: Validate the Model

Before generating code, validate your model:

Completeness Checks

  • Every entity has an ID field
  • Every relationship has clear ownership
  • Every workflow references existing entities
  • State machine covers all valid transitions

Consistency Checks

  • No duplicate entity names
  • No circular ownership dependencies
  • Enum values match what workflows expect
  • Foreign keys reference valid entities

Business Rule Checks

  • Constraints are modeled (e.g., “only leads can create projects”)
  • Validation logic is specified
  • Edge cases are handled in state machines

Step 6: Generate Code

With a validated model, generate your codebase:

Generated Output:
├── src/
│   ├── domain/
│   │   ├── entities/
│   │   │   ├── Task.ts
│   │   │   ├── Project.ts
│   │   │   ├── Team.ts
│   │   │   └── User.ts
│   │   ├── valueObjects/
│   │   │   ├── TaskId.ts
│   │   │   ├── TaskStatus.ts
│   │   │   └── UserRole.ts
│   │   └── events/
│   │       ├── TaskAssignedEvent.ts
│   │       └── TaskCompletedEvent.ts
│   │
│   ├── application/
│   │   ├── useCases/
│   │   │   ├── CreateTaskUseCase.ts
│   │   │   ├── AssignTaskUseCase.ts
│   │   │   └── CompleteTaskUseCase.ts
│   │   └── ports/
│   │       ├── TaskRepository.ts
│   │       └── NotificationService.ts
│   │
│   ├── adapters/
│   │   ├── persistence/
│   │   │   └── PostgresTaskRepository.ts
│   │   └── notifications/
│   │       └── SlackNotificationService.ts
│   │
│   └── infrastructure/
│       ├── http/
│       │   └── controllers/
│       │       └── TaskController.ts
│       └── config/
│           └── database.ts

├── tests/
│   ├── domain/
│   │   └── Task.test.ts
│   └── application/
│       └── AssignTaskUseCase.test.ts

└── package.json

Step 7: Extend and Refine

The generated code is a starting point. Add:

  • Business logic details: The generator creates structure; you fill in the rules
  • Error handling: Add try-catch, validation messages
  • Logging and metrics: Add observability
  • Tests: Expand on generated test stubs
  • UI components: If generating frontend, add styling and interactions

MDD Best Practices

1. Start small

Don’t model your entire system upfront. Start with one bounded context or feature, generate it, validate the approach, then expand.

2. Keep models simple

If a diagram is too complex to fit on one page, it’s too complex. Split it into multiple diagrams or simplify the design.

3. Iterate frequently

Model → Generate → Review → Refine → Repeat

Don’t try to get the model perfect on the first try.

4. Version your models

Treat models like code: store them in git, review changes, tag releases.

5. Use conventions

Consistent naming and stereotypes make generation predictable:

  • <<entity>> for domain entities
  • <<valueObject>> for value objects
  • <<service>> for domain services
  • <<useCase>> for application use cases
  • <<port>> for interfaces
  • <<adapter>> for implementations

Common MDD Patterns

Pattern: Entity + Repository + Use Case

Entity (domain) ←── Repository (port) ←── UseCase (application)

                    Adapter (infrastructure)

Pattern: Event-Driven State Changes

Command → UseCase → Entity.method() → DomainEvent → EventHandler

Pattern: CQRS (Command/Query Separation)

Model commands (writes) separately from queries (reads):

Commands:                    Queries:
CreateTask → TaskRepository  GetTasks → TaskQueryService
AssignTask → TaskRepository  GetTaskById → TaskQueryService

Tools for Model-Driven Development

ToolStrengthUse Case
EcosystemCodeUML → Full-stack codeTypeScript, React, Node.js
PlantUMLText-based diagramsDocumentation
Enterprise ArchitectEnterprise modelingLarge organizations
StarUMLDesktop UML editorOffline modeling

Frequently Asked Questions

Is MDD slower than just coding?

Initially, yes—you’re creating models first. But over time, MDD is faster because:

  • Less repetitive coding
  • Fewer bugs to fix
  • Easier refactoring (change the model, regenerate)

What about changes after generation?

You have options:

  1. Round-trip: Edit generated code, sync back to model (complex)
  2. Protected regions: Mark code sections the generator won’t overwrite
  3. Regenerate + merge: Use git to merge regenerated code with your changes
  4. Inheritance: Extend generated classes in separate files

Can I use MDD for existing projects?

Yes, but start small:

  1. Model one new feature or bounded context
  2. Generate it into a subdirectory
  3. Integrate with existing code via interfaces

Next Steps

Ready to try model-driven development? Start building free.