# Understanding Numo Through Examples Numo centralizes the documentation and presentation of key domain model elements. It describes domain models conceptually and allows for the addition of implementation details. Numo uses an abstract data structure called the DDDML DOM (Document Object Model), a tree structure of various node types. This structure is representable in JSON (JavaScript Object Notation) or YAML (YAML Ain't Markup Language), a more human-readable superset of JSON. In the following examples, we'll use YAML-based DDDML to describe domain models. ### Preparation You Might Need With Numo, we can support code generation tools to produce software code that closely aligns with the domain model. Additionally, Numo can automatically generate software documentation, database schemas, state machine diagrams, API specifications, and more. If you don't want to just read the documentation, but want to get hands-on and see what Numo can do for you, you might need to prepare the following tools: * Install [MUD](https://mud.dev/quickstart). As a MUD developer, I believe you may have already completed this step. * Install [Docker](https://docs.docker.com/engine/install/). * Install [Cursor IDE](https://www.cursor.com). If you want to verify the chemistry between DSL and AI yourself, you might need a programming assistant. Then, you can refer to MUD's ["Quick Start"](https://mud.dev/quickstart#installation) to create a simple MUD project and get it running. Next, you can open this project with an IDE and start your Numo magic journey. ### Hello World Create a `dddml` directory in your project root, then put a `Counter.yaml` file in this directory with the following content: ```yaml singletonObjects: Counter: properties: Value: type: u32 # We use "u32" here, which maps to Solidity's "uint32". # type: uint32 # Alternatively, you can use "uint32" directly. methods: Increase: shouldCreateOnDemand: true # This prevents the generation of "assert object exists" code. result: isObjectReturned: true # Returns the current value of the object. event: name: CounterIncreased properties: OldValue: type: u32 # "uint32" is also acceptable here. ``` In the root directory of your codebase, execute: ```shell docker run \ -v .:/myapp \ wubuku/dddappp-mud:master \ --dddmlDirectoryPath /myapp/dddml \ --boundedContextName Hello.Mud \ --mudProjectDirectoryPath /myapp \ --boundedContextJavaPackageName org.dddml.suiinfinitesea \ --javaProjectsDirectoryPath /myapp/mud-java-service \ --javaProjectNamePrefix hellomud \ --pomGroupId dddml.hellomud \ --enableMultipleMoveProjects ``` > **Hint** > > If you've run `dddappp-mud` before and are now encountering strange issues, you can try deleting the old containers and images first to ensure you're using the latest image: > > ```shell > docker rm $(docker ps -aq --filter "ancestor=wubuku/dddappp-mud:master") > docker rmi wubuku/dddappp-mud:master > ``` This operation will modify the MUD configuration file: `packages/contracts/mud.config.ts`, and generate the Solidity scaffold code for the business logic implementation corresponding to the method defined in the above model (`Counter.Increase`), located here: `packages/contracts/src/systems/CounterIncreaseLogic.sol`. You may have noticed the naming pattern of this file, which consists of the name of the entity + the name of the method + `Logic.sol` At this point, the code in this file might look like [this](https://gist.github.com/wubuku/d7a45b868cb8f21b74e41127baf3b28e). You'll find that there are already two functions in this file, with their signatures already written. You only need to fill in the function bodies. You don't even have to write this code yourself; you can let AI complete this "fill-in-the-blank" task. For example, in [Cursor IDE](https://www.cursor.com), you can do this: * Use the shortcut Cmd + A to select all the code in the current file. (I'm using macOS; for Windows systems, replace Cmd with Ctrl.) * Use the shortcut Cmd + L to open Cursor's CHAT window (I tested using the claude-3.5-sonnet model). * Enter `complete the functions` and press Enter. It's time to witness the magic. Here's the [code AI completed for me](https://gist.github.com/wubuku/2b3691a9af146316d2811774868eb932). The code generated by AI for me passed compilation on the first try, and I couldn't see any logical problems. Perfect! ### Blog Example In the `dddml` directory, create a `blog.yaml` file with the following content: ```yaml aggregates: Article: metadata: Preprocessors: ["CRUD_IT"] # Automatically generate Create/Update methods for articles CRUD_IT_NO_DELETE: true # But do not generate Delete methods id: name: Id type: u64 generator: class: sequence # We want to use an auto-generated sequence number as the article ID #tableName: ArticleIdGenerator # Default value properties: Author: type: address Title: type: String Body: type: String Comments: itemType: Comment entities: Comment: metadata: Preprocessors: [ "CRUD_IT" ] id: name: CommentSeqId type: u64 generator: class: sequence # Similarly, use an auto-generated sequence number as the comment ID # tableName: CommentSeqIdGenerator # Default value globalId: # Here we explicitly specify the two column names corresponding to the ID in the comment table, # instead of using default values columnNames: - ArticleId - CommentSeqId properties: Commenter: type: String Body: type: String ``` For developers with some object-oriented programming (OOP) experience, understanding the content expressed by the Numo model shouldn't be too difficult. However, you might still find writing DSL by hand a bit cumbersome. To address this, we plan to implement a GUI Numo modeler in the future. At the same time, we are also exploring the possibilities of AI-assisted modeling. Perhaps in the near future, you'll only need to use prompts similar to the one below to get a `blog.yaml` file like the one above: ```text Please design a Numo model for a Dapp using the MUD framework. This is a blog application with the following features: 1. Main entities: - Article - Comment 2. Article entity: - Properties: title, body, author - Operations: support creation and modification, but not deletion 3. Comment entity: - Properties: comment content, commenter name - Association: must be associated with a specific article, cannot exist independently - Operations: support full CRUD functionality (Create, Read, Update, Delete) 4. Special requirements: - Commenter name: users can input a display name when posting a comment - Article author: consider using an address type to represent the author Please generate a model that conforms to the Numo specification based on these requirements. ``` With such a prompt, we can expect AI to generate the `blog.yaml` file above for us. By reviewing the prompt alongside the YAML file, you should gain a clearer understanding of its meaning—so I won't elaborate further. Now, run the `docker run` command again, and you can see what files with the `Logic.sol` name suffix have been generated. If the generation is successful, it should include: * `ArticleCreateLogic.sol` * `ArticleUpdateLogic.sol` * `ArticleAddCommentLogic.sol` * `ArticleUpdateCommentLogic.sol` * `ArticleRemoveCommentLogic.sol` This time, AI doesn't even have a chance to show off. All the function implementations have been written for you. As we mentioned earlier, if the CRUD operations on entities are exactly the business logic your application needs, then you don't need to write anything else yourself. #### Let's Talk About "Data Types" Briefly In Numo, you can use all [field types supported by the MUD data model](https://mud.dev/store/data-model#field-types) to declare property and parameter types. Moreover, Numo supports even more data types. This is because Numo's type system is a higher-level abstraction that can map abstract types in the "domain model" to specific implementation types when generating code. Specifically, we use the following "basic data types" in Numo models: ##### 1. Integer Types For example: - `u8` (can also be written as `uint8`) - `u16` (can also be written as `uint16`) - `u32` (can also be written as `uint32`) - `u64` (can also be written as `uint64`) - `u256` (can also be written as `uint256`) - `int32` (can also be written as `i32`) - `uint32` (equivalent to `u32`) ##### 2. Boolean Type - `bool` ##### 3. String Type - `string` (can also be written as `String`) ##### 4. Address Type - `address` ##### 5. Bytes Type - `bytes` ##### 6. Array Types For example: - `uint8[]` - `uint32[]` - `uint64[]` - `uint256[]` ##### 7. Enumeration Types The following Numo document describes an enumeration object `Weekday`: ```yaml enumObjects: Weekday: baseType: u8 values: Monday: value: 1 Tuesday: value: 2 Wednesday: value: 3 Thursday: value: 4 Friday: value: 5 Saturday: value: 6 Sunday: value: 7 ``` The base type of this enumeration is `u8`, an unsigned 8-bit integer. Values from 1 to 7 represent Monday through Sunday, with `3` indicating Wednesday. In the MUD version of our Numo code generation tool, such enumerations are converted into a Solidity `library` with constant definitions like `uint8 constant MONDAY = 1;`. > According to the Numo specification, specifying a `baseType` for enumeration objects is optional. The code generation tool can produce suitable code for Numo-defined enumerations based on language features and team coding standards. > > Languages like Java and C# have the `enum` keyword, while some others do not. In these cases, the Numo tool might substitute the enumeration object with the declared `baseType`. > Sometimes this is **not** a bad choice, as it may bring convenience in terms of serialization and persistence handling. ##### 8. Composite Types (Value Objects) We can also construct composite types (Value Objects) using these basic types, as demonstrated in the example below. ### Example from the Game Infinite Seas The following model is from our fully on-chain game [Infinite Seas](http://infiniteseas.io), focusing on the `SkillProcess` entity and related value objects. The `SkillProcess` definition is simplified here, highlighting its properties and the `Create` method. ```yaml enumObjects: SkillType: baseType: u8 values: Farming: value: 0 Woodcutting: value: 1 Crafting: value: 6 valueObjects: SkillProcessId: properties: SkillType: type: SkillType PlayerId: type: u256 SequenceNumber: type: u8 ItemIdQuantityPair: properties: ItemId: type: u32 # ID of the item Quantity: type: u32 # Quantity of the item aggregates: SkillProcess: id: name: SkillProcessId type: SkillProcessId properties: ItemId: # ID of the item produced by the process type: u32 StartedAt: # Time when the process started type: u64 CreationTime: # Time required to produce the product type: u64 Completed: # Whether the process is completed type: bool EndedAt: # Time when the process ended type: u64 BatchSize: # Batch size of the product produced type: u32 Existing: # Whether the process already exists type: bool # When all other properties of a legally existing object may have default values, # we need a specialized property to identify whether it exists or not ProductionMaterials: # Input materials for production itemType: ItemIdQuantityPair tableName: SkillPrcMtrl # Here we give a short name to the table that stores the state of this property. # If we don't set it, the code generation tool will also give this table a default name, but it might be longer. # And MUD will truncate overly long table names, which may cause naming conflicts. description: "Actual input materials for production" methods: Create: isCreationCommand: true # parameters: event: name: SkillProcessCreated ``` In Infinite Seas, players can participate in various skill-based activities like farming, woodcutting, mining, and crafting. However, the number of concurrent production processes is limited. For instance, a player can manage up to two farming processes, one woodcutting process, one mining process, and one crafting process simultaneously. The `SkillProcess` entity is designed to manage these activities. The "domain ID" of `SkillProcess` is a value object called `SkillProcessId`, comprising three components: 1. `SkillType`: The type of skill, such as farming or crafting. 2. `PlayerId`: The ID of the player executing the process. 3. `SequenceNumber`: The sequence number of the process, starting at `0` for the first process and increasing with the player's level. Unlike the `Article` entity in the previous example, the `SkillProcess` entity's `id` property lacks `generator` information, meaning the ID must be provided by the frontend during creation. We also defined a value object `ItemIdQuantityPair`, which includes `ItemId` and `Quantity`. This type is used throughout the game's model to represent item ID and quantity combinations, simplifying the model's expression. For the `SkillProcess` entity, we opted not to use the `CRUD_IT` preprocessor to automatically add CRUD methods. Instead, we defined a `Create` method, marked as a "creation command" (`isCreationCommand: true`). The frontend provides the entity's ID (`SkillProcessId`), while the backend determines other necessary information, eliminating the need for explicit parameters in the `Create` method. Further explanations of the model are included in the YAML comments above. Does this model seem more complex than the previous ones? Are you curious about how AI will handle it? Open the `SkillProcessCreateLogic.sol` file using Cursor. Before AI steps in, the file might look like [this](https://gist.github.com/wubuku/ac4f965f5c467190e89cf2128fe0ef7e). The tool generates numerous comments, which might seem excessive, but they are intended to guide AI (and you) in completing the business logic code. I used this prompt to guide AI: > Read the comments of the current file, and the file I referenced @SkillType.sol, and complete the functions. The necessary files, like `SkillType.sol`, are hinted at in the generated code's comments. The AI-generated code looked like [this](https://gist.github.com/wubuku/f1b71f20d448edb2e10f53232fa7cb10) and compiled successfully on the first try, with no apparent logical issues. Surprised? Unexpected? đŸ˜„ > **Hint** > > The comments might mention some files that are not used, or omit some that should be used. > However, generally speaking, > developers should be able to judge which files are needed to implement the current business logic. > DDD advocates for the entire development team to maintain the same domain model. > If the team follows this best practice, developers can provide better guidance to AI. ### More Examples In the [`dddml`](https://github.com/wubuku/hello-mud/tree/main/dddml) directory of this [repository](https://github.com/wubuku/hello-mud), you can find more examples of Numo models. Most of these examples come from actual development projects and have been used in production environments. ### Conclusion Through practical examples, we showed how Numo enables developers to swiftly construct complex domain models and generate corresponding business logic code. For MUD developers, Numo is a powerful tool that significantly boosts efficiency in domain modeling and business logic implementation. By leveraging domain-specific languages and low-code development methods, teams can concentrate more on system analysis and high-level design. The synergy between Numo and AI not only makes coding more efficient but also ensures that the implemented code aligns with the domain model, which is vital for developing complex applications. As AI technology continues to evolve, we anticipate that Numo and its associated low-code tools will become more prevalent and deeply integrated into software development. Whether through automated code generation or intelligent domain modeling, developers will be able to bring their ideas to life more effectively. The future of combining DSL and AI holds great promise.