Ubiquitous Language: How to implement it in your code

“Ubiquitous Language” in the context of Domain-Driven Design (DDD) is a fancy word for common language. To put it simple it means having a set of words that everyone on a project agrees to use. This way, everyone understands each other better, and there’s less confusion. It’s like having a team dictionary for a project. Imagine a group of people making a game. They decide to call the main character “Hero” and not “Player” or “Character.” Now, whenever someone says “Hero,” everyone knows they’re talking about the main character, and there’s no confusion.

In other words “Ubiquitous Language” refers to a shared vocabulary used by all team members—developers, domain experts, stakeholders, and other participants—within a given project. This common language ensures that everyone speaks in terms consistent with the domain model, eliminating ambiguities and misunderstandings.

This article will delve into the need for a Ubiquitous Language, explore its foundational principles, and provide practical steps for implementation. The goal is to help you get comfortable to practically implement Ubiquitous Language in your code as well as your documentions and conversations.

Why Ubiquitous Language

Importance of Ubiquitous Language in software development

In the vast ecosystem of software development, clear communication is paramount. By having a ubiquitous language, teams can ensure that concepts and terms are not only understood but also uniformly implemented throughout the entire codebase, reducing potential pitfalls and misinterpretations.

The problem of ambiguity in software development

Ambiguity means something can be understood in more than one way. For example, if someone says, “I saw a bat,” you might wonder if they saw a flying animal or a piece of sports equipment. It’s unclear.

Ambiguity is a big problem in software projects. If people use words that can mean different things, it can cause mistakes in the software. It’s like playing a game of “telephone” – the message can get mixed up!

Example of Ambiguity Problem:

Suppose you’re working on a banking app.

The Stakeholder’s Perspective (Someone from the Bank):

They view a “transaction” as any time someone uses the app. This could be checking an account balance, updating personal details, or transferring money. For them, it’s about how many times users are engaging with their app.

The Programmer’s Initial Perspective:

The programmer understands “transaction” to mean just the act of moving money from one account to another. They would then build features and write code with this specific idea in mind.

Now, if the stakeholder and the programmer don’t sit down and clarify what they mean by “transaction”, there’s going to be a problem.

Result: The programmer might create a feature that only tracks money transfers, completely missing out on other types of user activities. When the stakeholder reviews the app, they might ask, “Why are we only seeing money transfers? I thought we’re tracking all app activities!”

And here’s another twist:

Another Programmer Joins the Team:

A new coder joins the team, and they have their own understanding of “transaction”. Maybe they think a “transaction” includes setting up recurring payments or depositing a check using the app’s camera feature.

If the team doesn’t clarify the agreed meaning, this new coder might start building features or making changes based on their own definition. This can lead to even more confusion and inconsistencies in the app.

Clear communication is crucial. If team members, whether they are stakeholders or programmers, don’t have a shared understanding of key terms, the final product – in this case, the banking app – might not meet expectations, might be inconsistent, or might require a lot of rework. It’s like building a puzzle where everyone has pieces from different sets. The pieces won’t fit together correctly unless everyone is working from the same box.

How Domain-Driven Design (DDD) Helps:

Domain-Driven Design (DDD) is like making a shared dictionary for a project. It ensures everyone uses the same words with the same meanings. This helps make sure the software fits what the business needs.

Fundamentals of Building a Ubiquitous Language

Collaborative Approach: Involving both developers and domain experts

The creation of a ubiquitous language isn’t a solo endeavor. It requires collaboration between those who understand the technical aspects (developers) and those who are experts in the domain (business analysts, stakeholders). By fostering open dialogue, terms and concepts can be accurately defined and understood by all.

Continuous refinement: Evolving the language with project changes

As projects grow and evolve, so too should the ubiquitous language. New functionalities might introduce new terms or modify the meanings of existing ones. Regular reviews and updates to the language are crucial to ensure it remains relevant and comprehensive.

Documentation: Maintaining a clear, accessible record of the language

Once established, the ubiquitous language should be well-documented. This serves as a reference for all team members and can be particularly valuable for onboarding new members. This documentation should be easy to access, and regularly updated.

Consistency: Ensuring the language is used uniformly across the codebase

Once a term is defined within the ubiquitous language, it should be consistently used throughout the project. This includes not just the code but also documentation, user interfaces, and verbal or written communications among team members.

Practical Steps to Implement a Ubiquitous Language

Step 1: Domain modeling sessions with stakeholders

Start by organizing domain modeling sessions where developers and domain experts collaborate. Using techniques like Event Storming or brainstorming, capture essential terms, events, and processes of the domain.

there’s a structured approach to domain-driven design (DDD) that often parallels language constructs. When creating a model from the Ubiquitous Language, we can typically associate different parts of speech with different model components:

  1. Nouns: These often represent Entities or Value Objects. Entities have a distinct identity that runs through time and different states, while Value Objects are descriptive aspects of the domain with no conceptual identity. Note that all nouns doesn’t need to be Entities or Value Objects, these also can be Attributes or properties of an entity or value object.
    • Example: In a chess game, “Board,” “Square,” and “Player” could be entities, while “Piece” might be a value object.
  2. Verbs: These can often be associated with Methods or Functions that act upon or are behaviors of entities or value objects. Again, all verbs doesn’t need to be functions, these also can be lead to and state so whey can be a sign of Attributes or properties of an entity or value object.
    • Example: “Move” could be a method associated with the “Piece” entity, as in Piece.move(), on the other hand “check” is a verb but it maght be a sign that you need to create a property for state of being “in check” ex. “isInCheck: boolean
  3. Adjectives: These might define Attributes or properties of an entity or value object. They can also be associated with specific constraints or rules around a concept.
    • Example: In the context of a user profile in a different domain, “active” or “inactive” might describe the status of a user.
  4. Adverbs: These might describe how a method or function should be performed, often indicating some form of domain-specific rule or constraint.
    • Example: In a different domain like order processing, “quickly” might modify how an order is shipped, leading to methods like Order.shipQuickly().
  5. Prepositions and phrases: These can often help in understanding the relationships and associations between different entities and value objects.
    • Example: “Belongs to” or “is part of” can help establish relationships, such as a “Piece” belongs to a “Player.”
  6. Aggregates: In DDD, an aggregate is a cluster of domain objects that can be treated as a single unit. The root entity of this cluster can be derived from the Ubiquitous Language. For instance, terms that are often spoken of in a collective sense, or where certain invariants hold true as a group.
    • Example: In a chess game, the “Board” might be an aggregate root, encapsulating the rules and invariants related to the pieces on the board.

When building a domain model using DDD, the Ubiquitous Language and the associated glossary terms derived from discussions with domain experts play a pivotal role. This ensures the software model aligns with the business domain’s realities, leading to software that’s more intuitive, maintainable, and adaptable to changes.

Step 2: Create a glossary of terms that everyone agrees upon

Consolidate the gathered terms into a glossary. Each term should have a clear, concise definition. Make this glossary accessible to everyone involved in the project, from developers to stakeholders.

step 3: Code conventions: Naming classes, methods, and variables consistently

Implementing a ubiquitous language goes beyond verbal communication; it should reflect in the code itself. Adopt naming conventions that align with the agreed-upon terms, ensuring that code remains self-descriptive and coherent.

step 4: Use the same language in user interfaces, tests, and documentation

Ensure that UI labels, test scenarios, and project documentation are in harmony with the ubiquitous language. This uniformity reduces cognitive friction and reinforces the shared understanding of terms across different facets of the project.

step 5 : Encourage team members to use the language in meetings, emails, and other communications

A ubiquitous language is effective only if it’s consistently used. Encourage its usage in all project-related communications. Over time, this will cement the terms in the team’s vocabulary.

Step-by-Step Guide for Using Ubiquitous Language

Let’s try to see how we would practically we would make Ubiquitous Language for a “Chess” software.

Step 1: Domain modeling sessions with stakeholders

  • Gather domain experts (chess masters, enthusiasts) and developers.
  • Discuss the core concepts of chess: board, squares, and chess pieces.

Step 2: Create a glossary of terms that everyone agrees upon

Chess has its own rich vocabulary that can serve as a Ubiquitous Language when developing software around the game. Here are some chess-specific terms that might be relevant:

Here is a sample of the categorized glossary :

Nouns (Entities or Value Objects /Attributes or properties of an entity or value object)

  • Board: The 8×8 grid where the game is played.
  • Square: One of the 64 spaces on the board.
  • Piece: General term for any of the chess figures.
  • Pawn, Rook, Knight, Bishop, Queen, King: The six different types of chess pieces.
  • File: A vertical column of squares on the board.
  • Rank: A horizontal row of squares on the board.
  • Diagonal: A line of squares of the same color, from one edge of the board to an adjacent edge.
  • Fianchetto: A pawn structure where a pawn is moved one square forward from its starting position, allowing the bishop to be developed behind it.
  • Pin: A situation where a piece cannot move without exposing a more valuable piece to capture.
  • Fork: When a single piece attacks two or more enemy pieces at the same time.

Verbs (Methods or Functions)

  • Move: Action of relocating a piece from one square to another
  • Capture: When one piece takes another.
  • Check: When the king is under direct threat of capture.
  • Checkmate: When the king is in check and cannot make a legal move to escape.
  • Stalemate: When a player has no legal moves left and their king isn’t in check.
  • Castling: A special move involving the king and a rook.
  • Promote (from Promotion): When a pawn reaches the opposite side of the board and is exchanged for another piece, usually a queen.

Adjectives (Attributes or properties of an entity or value object)

  • Opening: Describing the initial series of moves in a game.
  • Midgame: Describing the middle portion of a game after the opening.
  • Endgame: Describing the final phase of the game, typically when only a few pieces are left.

Prepositions and Phrases

(None in the given list)


Aggregates

  • Board (as an aggregate root): It can be seen as a collection of squares and pieces, enforcing the rules and invariants related to the game state.

Entities: Squares

Value Objects: Piece

step 3: Code conventions: Naming classes, methods, and variables consistently

  • Use class names and variable names that align with our domain terms.
  • Methods should reflect domain actions, e.g., movePiece(fromSquare: Square, toSquare: Square), canCapture, isInCheck, ScanDiagonal

step 4: Use the same language in user interfaces, tests, and documentation

TypeScript Code Implementation

TypeScript
// Value Objects
abstract class ChessPiece {
    color:string;
    type:"Pawn" | "Rook" | "Knight" | "Bishop" | "Queen" | "King"
    abstract isValidMove(from: Square, to: Square): boolean;
    // Other shared behavior among chess pieces...
}

class Pawn extends ChessPiece {
    isValidMove(from: Square, to: Square): boolean {
        // Implement pawn-specific movement rules
        return true; // Placeholder
    }
}

class Rook extends ChessPiece {
    isValidMove(from: Square, to: Square): boolean {
        // Implement rook-specific movement rules
        return true; // Placeholder
    }
}

// ... Similarly for other chess pieces

// Entity
class Square {
    rank: string;
    file: string;
    constructor(public coordinate: string, public piece?: ChessPiece) {
    // coordinate is actually the Id of the Entity
    }

    setPiece(piece: ChessPiece) {
        this.piece = piece;
    }

    removePiece():  {
        this.piece = undefined;
    }
}

// Aggregate Root
class Board {
    private squares: Square[] = [];

    constructor() {
        // Initialize the board with squares and default pieces placement
    }

    movePiece(fromCoord: string, toCoord: string): boolean {
        const fromSquare = this.getSquare(fromCoord);
        const toSquare = this.getSquare(toCoord);

        if (!fromSquare?.piece) return false; // No piece to move

        if (fromSquare.piece.isValidMove(fromSquare, toSquare)) {
            const piece = fromSquare.getPiece();
            if (!piece) return false;
            fromSquare.removePiece()
            toSquare.setPiece(piece);
            return true;
        }

        return false; // Invalid move
    }

    private getSquare(coord: string): Square {
        // Logic to find and return the square by its coordinate
    }
}

// Usage
const board = new Board();
board.movePiece('E2', 'E4');

This example demonstrates how the Ubiquitous Language informs our code structure, naming conventions, and design decisions. Concepts from the domain (like Board, Square, ChessPiece) naturally map to entities and value objects in our model, and our code remains descriptive and aligned with domain logic.

Challenges and Pitfalls to Avoid

Overengineering: Keep it simple and relevant

While it’s essential to have a shared language, avoid the temptation to overcomplicate it. Stick to terms that are crucial for understanding and implementing the domain logic.

Ambiguity: Ensure every term has a clear, singular definition

Ambiguity defeats the purpose of a ubiquitous language. When defining terms, ensure they are unambiguous and can’t be misinterpreted within the project’s context.

Stagnation: The language should evolve with the project

Refrain from treating the ubiquitous language as a static artifact. As the project grows, the language should be revisited and refined to cater to new insights and requirements.

Lack of buy-in: Ensure all stakeholders, especially senior management, are onboard

Conclusion

Embracing a Ubiquitous Language is more than just adopting a set of terms; it’s about fostering a shared understanding that bridges the gap between different team members, from coders to stakeholders. As we’ve delved into, the benefits of this approach are manifold, ranging from clearer communication to more maintainable code. Challenges may arise, but with conscious effort and collaboration, they can be navigated successfully. As the digital landscape becomes increasingly complex, ensuring that everyone is “speaking the same language” has never been more crucial. By committing to a shared vocabulary, teams can not only avoid pitfalls and misunderstandings but also pave the way for more streamlined, cohesive, and effective projects.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top