Structuring the Project

Lino was developed to simplify the creation of scalable and modular projects efficiently. It provides a well-structured solution with a clear separation of responsibilities across layers and is ready to grow as your project’s needs evolve.


When creating a project with Lino, you generate a .NET solution organized according to best practices in architecture and modularization, focusing on performance, scalability, and ease of maintenance.

Creating a New Project

The lino project command simplifies the process of creating new .NET projects in an easy and efficient way. With this command, you can set up the project structure, select required dependencies, and define language and infrastructure settings.

To create a new project, use the following command:

lino project new

During execution, the command-line interface will prompt for the following information:

  • Project Namespace: Defines the main namespace for the solution.
  • Display Name: The friendly name displayed in interfaces.
  • Programming Language: Currently, only C# (.NET) is supported.
  • Development Stack: Currently, .NET 9 with Aspire.
  • Use Code Analyzers? Choose Yes or No.
  • Use Distributed Cache? Choose Yes or No.
  • Use Asynchronous Communication? Choose Yes or No.
  • Data Language: Defines the language used for entity names and other system data.
  • Supported Application Languages: Allows you to add support for up to 10 languages for internationalization (i18n).
  • Default Language: Defines the main language used in APIs, return messages, validations, and the user interface.

After confirming the information, Lino will automatically create the project structure, as shown below:

MyApp/
β”œβ”€β”€ MyApp.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Aspire/
β”‚   β”‚   β”œβ”€β”€ AppHost/
β”‚   β”‚   β”‚   └── MyApp.AppHost.csproj
β”‚   β”‚   └── ServiceDefaults/
β”‚   β”‚       └── MyApp.ServiceDefaults.csproj
β”‚   └── Services/
β”‚       └── Shared/
β”‚           β”œβ”€β”€ API/
β”‚           β”‚   └── MyApp.Shared.API.csproj
β”‚           β”œβ”€β”€ Application/
β”‚           β”‚   └── MyApp.Shared.Application.csproj
β”‚           β”œβ”€β”€ Domain/
β”‚           β”‚   └── MyApp.Shared.Domain.csproj
β”‚           β”œβ”€β”€ Infrastructure/
β”‚           β”‚   └── MyApp.Shared.Infrastructure.csproj
β”‚           └── Infrastructure.Persistence/
β”‚               └── MyApp.Shared.Infrastructure.Persistence.csproj
└── tests/
    

Code Analyzers

Static code analyzers are powerful tools that help ensure code quality and consistency during development. They do this by inspecting source code without execution, detecting errors, style issues, and other inconsistencies.

When you choose to enable code analyzers during project creation, the command-line interface will automatically configure the following packages for you:

  • StyleCop.Analyzers: Performs code style checks, such as indentation, spacing, and naming conventions.
  • SonarAnalyzer.CSharp: A set of code quality and security rules that helps detect common bugs and potential vulnerabilities in the code.
  • Roslynator.Analyzers: Offers various C# code quality and improvement rules, helping to find opportunities for refactoring and optimization.

Benefits of Code Analyzers:

  • Improved Quality: Helps keep the code clean, readable, and free from common issues.
  • Error Prevention: Detects errors before execution, allowing the developer to fix them early in the process.
  • Standardization: Ensures all developers follow the same conventions and style rules, improving code consistency.
  • Refactoring Support: Helps refactor code by suggesting improvements.

By enabling code analyzers, you proactively improve the quality of your code and reduce the risk of failures in the production environment.

Distributed Cache

Distributed caching is a technique used to improve application performance and scalability by storing frequently accessed data in a cache that is separate from the database. This allows application instances to efficiently share cached data, ensuring high availability and reducing response times.

If you choose to enable distributed caching during project creation, Redis will be integrated into your Aspire container, providing a high-availability caching system.

Benefits of Distributed Caching:

  • Improved Performance: Reduces query response time by minimizing database access.
  • Scalability: Allows the application to scale horizontally, as the cache can be accessed from multiple instances seamlessly.
  • High Availability: With Redis, the cache remains available even in case of failure, providing a reliable system for distributed applications.
  • Cost Reduction: Reduces the load on databases and systems, eliminating the need to recalculate the same results for repeated queries.

By enabling distributed caching, you will significantly improve your application’s performance, ensuring faster response times for users and reducing the load on backend servers.

Asynchronous Communication

Asynchronous communication is an approach that allows systems and components to exchange data without blocking the execution of other tasks. This is especially useful in distributed systems and high-load scenarios where efficiency and resilience are critical.

If you choose to enable asynchronous communication, RabbitMQ will be integrated into your project, and MassTransit will be configured to simplify asynchronous communication within your application.

Benefits of Asynchronous Communication:

  • Improved Performance: Allows operations to be executed in parallel, without blocking other tasks.
  • Scalability: Helps the system scale, processing large amounts of data and concurrent users without sacrificing performance.
  • Resilience: In case of temporary failures, asynchronous communication allows messages to be retried or stored for later processing.
  • Decoupling: Systems can communicate without requiring immediate responses, improving flexibility and organization.

By integrating RabbitMQ and using MassTransit, communication between components will be more efficient and reliable, ensuring scalability and flexibility for your application.

Next Steps

Now that you have created your .NET project using Lino, open it in your favorite code editor. You can use Visual Studio, Visual Studio Code, or any IDE of your choice.

Once the project is open, you can start adding and configuring the services that make up your application. This is the next step in the development process.

In the next section, we will show you how to create and configure these services in your new project, preparing it for growth in an organized and scalable way based on your application's needs.

Create and Manage Services

After creating a project, the next step is to add services. Lino offers a simple and intuitive way to create services for monolithic systems or microservices architectures.

To create a new service, use the following command:

lino service new

During the process, the CLI will ask for the following information:

  • Service Namespace: Defines the service name and namespace.
  • Display Name: Friendly name that will be shown in the interface.
  • Service Type: Choose between simple or modularized.
  • Database: Choose between PostgreSQL or SQL Server.

If you choose a simple service type, the CLI will also request the following:

  • Architecture Style: Currently, only Clean Architecture is supported.
  • Use Strongly Typed IDs? Choose yes or no.

Service Types

Simple Service: A leaner structure, ideal for monolithic projects or microservices with clear and isolated responsibilities.

Modularized Service: Ideal for larger systems that require more organization and scalability, allowing the service to be divided into smaller, focused modules, making maintenance and evolution easier.

Regardless of type, each service will have its own database. Strongly typed ID support is standard for simple services; in modularized services, this decision is made per module.

Lino's structure is flexible, allowing the creation of both simple and modularized services within the same project.

Architecture Style

All services in Lino use Clean Architecture, ensuring best practices for separation of concerns, ease of maintenance, and system evolution.

Main benefits of Clean Architecture:

  • Decoupling: Business logic is isolated from technical details, increasing flexibility and testability.
  • Maintainability: Clear separation between layers, making changes easier without impacting the whole system.
  • Testability: Separation of responsibilities facilitates the creation of unit and integration tests.
  • Scalability: Components can be modified or replaced without affecting business logic.

If you choose to create a simple service with Clean Architecture, the project structure will look like:

MyApp/
β”œβ”€β”€ MyApp.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Aspire/
β”‚   β”‚   β”œβ”€β”€ AppHost/
β”‚   β”‚   β”‚   └── MyApp.AppHost.csproj
β”‚   β”‚   └── ServiceDefaults/
β”‚   β”‚       └── MyApp.ServiceDefaults.csproj
β”‚   β”œβ”€β”€ Integrations/
β”‚   β”‚   └── Internal/
β”‚   β”‚       └── MySimpleService/
β”‚   β”‚           └── Http/
β”‚   β”‚               β”œβ”€β”€ Clients/
β”‚   β”‚               β”‚   └── MyApp.Integrations.MySimpleService.Http.Clients.csproj
β”‚   β”‚               └── Contracts/
β”‚   β”‚                   └── MyApp.Integrations.MySimpleService.Http.Contracts.csproj
β”‚   └── Services/
β”‚       β”œβ”€β”€ Shared/
β”‚       β”‚   β”œβ”€β”€ API/
β”‚       β”‚   β”‚   └── MyApp.Shared.API.csproj
β”‚       β”‚   β”œβ”€β”€ Application/
β”‚       β”‚   β”‚   └── MyApp.Shared.Application.csproj
β”‚       β”‚   β”œβ”€β”€ Domain/
β”‚       β”‚   β”‚   └── MyApp.Shared.Domain.csproj
β”‚       β”‚   β”œβ”€β”€ Infrastructure/
β”‚       β”‚   β”‚   └── MyApp.Shared.Infrastructure.csproj
β”‚       β”‚   └── Infrastructure.Persistence/
β”‚       β”‚       └── MyApp.Shared.Infrastructure.Persistence.csproj
β”‚       └── MySimpleService/
β”‚           β”œβ”€β”€ API/
β”‚           β”‚   └── MyApp.MySimpleService.API.csproj
β”‚           β”œβ”€β”€ Application/
β”‚           β”‚   └── MyApp.MySimpleService.Application.csproj
β”‚           β”œβ”€β”€ Domain/
β”‚           β”‚   └── MyApp.MySimpleService.Domain.csproj
β”‚           β”œβ”€β”€ Infrastructure/
β”‚           β”‚   └── MyApp.MySimpleService.Infrastructure.csproj
β”‚           β”œβ”€β”€ Infrastructure.Persistence/
β”‚           β”‚   └── MyApp.MySimpleService.Infrastructure.Persistence.csproj
β”‚           └── IntegrationEvents/
β”‚               └── MyApp.MySimpleService.IntegrationEvents.csproj
└── tests/
    └── Services/
        └── MySimpleService/
            β”œβ”€β”€ IntegrationTests/
            β”‚   └── MyApp.MySimpleService.IntegrationTests.csproj
            └── UnitTests/
                └── MyApp.MySimpleService.UnitTests.csproj
    

If you choose to create a modularized service, the project will be structured to allow the organized addition of new modules:

MyApp/
β”œβ”€β”€ MyApp.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Aspire/
β”‚   β”‚   β”œβ”€β”€ AppHost/
β”‚   β”‚   β”‚   └── MyApp.AppHost.csproj
β”‚   β”‚   └── ServiceDefaults/
β”‚   β”‚       └── MyApp.ServiceDefaults.csproj
β”‚   └── Services/
β”‚       β”œβ”€β”€ Shared/
β”‚       β”‚   β”œβ”€β”€ API/
β”‚       β”‚   β”‚   └── MyApp.Shared.API.csproj
β”‚       β”‚   β”œβ”€β”€ Application/
β”‚       β”‚   β”‚   └── MyApp.Shared.Application.csproj
β”‚       β”‚   β”œβ”€β”€ Domain/
β”‚       β”‚   β”‚   └── MyApp.Shared.Domain.csproj
β”‚       β”‚   β”œβ”€β”€ Infrastructure/
β”‚       β”‚   β”‚   └── MyApp.Shared.Infrastructure.csproj
β”‚       β”‚   └── Infrastructure.Persistence/
β”‚       β”‚       └── MyApp.Shared.Infrastructure.Persistence.csproj
β”‚       └── MyModularService/
β”‚           β”œβ”€β”€ Host/
β”‚           β”‚   └── MyApp.MyModularService.Host.csproj
β”‚           β”œβ”€β”€ Infrastructure/
β”‚           β”‚   └── MyApp.MyModularService.Infrastructure.csproj
β”‚           └── Modules/
└── tests/
    

Strongly Typed IDs

Strongly typed IDs are a practice to increase code safety and clarity by using specific types for identifiers (IDs) instead of generic types like int or guid.

With strongly typed IDs, incorrect use of identifiers is avoided, reducing common mistakes. For example, a user ID is represented by a dedicated type instead of a simple integer.

Advantages of strongly typed IDs:

  • Type Safety: Ensures correct use of IDs, preventing the mixing of different types.
  • Code Clarity: Each ID type is represented by a specific class, improving readability.
  • Ease of Refactoring: Changes to an ID's format are applied only in its type definition, keeping the rest of the code safe.
  • Reduction of Errors: Prevents using IDs in the wrong context.

Next Steps for Modularized Services

After creating a modularized service, the next step is to add modules. Modules organize business logic and infrastructure independently, making the system more scalable and organized.

In the next section, we will learn how to create and manage modules within modularized services, taking full advantage of the flexibility and scalability of this approach.

Creating and Managing Modules

After creating a modular service, the next step is to add modules to it. Modules allow you to organize business logic independently, bringing even more scalability and organization to the system.

To create a new module, use the following command:

lino module new

During execution, the CLI will prompt for the following information:

  • Service: Defines which service the new module will be created for.
  • Module Namespace: Defines the name and namespace of the module.
  • Display Name: The user-friendly name displayed in the interfaces.
  • Architectural Style: Currently, only Clean Architecture.
  • Use Strongly Typed ID? Choose between Yes or No.

At the end, Lino will generate the new module while keeping the structure of your modular service intact:

MyApp/
β”œβ”€β”€ MyApp.sln
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Aspire/
β”‚   β”‚   β”œβ”€β”€ AppHost/
β”‚   β”‚   β”‚   └── MyApp.AppHost.csproj
β”‚   β”‚   └── ServiceDefaults/
β”‚   β”‚       └── MyApp.ServiceDefaults.csproj
β”‚   β”œβ”€β”€ Integrations/
β”‚   β”‚   └── Internal/
β”‚   β”‚       └── MyModularService/
β”‚   β”‚           └── MyModule/
β”‚   β”‚               └── Http/
β”‚   β”‚                   β”œβ”€β”€ Clients/
β”‚   β”‚                   β”‚   └── MyApp.Integrations.MyModularService.MyModule.Http.Clients.csproj
β”‚   β”‚                   └── Contracts/
β”‚   β”‚                       └── MyApp.Integrations.MyModularService.MyModule.Http.Contracts.csproj
β”‚   └── Services/
β”‚       β”œβ”€β”€ Shared/
β”‚       β”‚   β”œβ”€β”€ API/
β”‚       β”‚   β”‚   └── MyApp.Shared.API.csproj
β”‚       β”‚   β”œβ”€β”€ Application/
β”‚       β”‚   β”‚   └── MyApp.Shared.Application.csproj
β”‚       β”‚   β”œβ”€β”€ Domain/
β”‚       β”‚   β”‚   └── MyApp.Shared.Domain.csproj
β”‚       β”‚   β”œβ”€β”€ Infrastructure/
β”‚       β”‚   β”‚   └── MyApp.Shared.Infrastructure.csproj
β”‚       β”‚   └── Infrastructure.Persistence/
β”‚       β”‚       └── MyApp.Shared.Infrastructure.Persistence.csproj
β”‚       └── MyModularService/
β”‚           β”œβ”€β”€ Host/
β”‚           β”‚   └── MyApp.MyModularService.Host.csproj
β”‚           β”œβ”€β”€ Infrastructure/
β”‚           β”‚   └── MyApp.MyModularService.Infrastructure.csproj
β”‚           └── Modules/
β”‚               └── MyModule/
β”‚                   β”œβ”€β”€ API/
β”‚                   β”‚   └── MyApp.MyModularService.MyModule.API.csproj
β”‚                   β”œβ”€β”€ Application/
β”‚                   β”‚   └── MyApp.MyModularService.MyModule.Application.csproj
β”‚                   β”œβ”€β”€ Domain/
β”‚                   β”‚   └── MyApp.MyModularService.MyModule.Domain.csproj
β”‚                   β”œβ”€β”€ Infrastructure/
β”‚                   β”‚   └── MyApp.MyModularService.MyModule.Infrastructure.csproj
β”‚                   β”œβ”€β”€ Infrastructure.Persistence/
β”‚                   β”‚   └── MyApp.MyModularService.MyModule.Infrastructure.Persistence.csproj
β”‚                   └── IntegrationEvents/
β”‚                       └── MyApp.MyModularService.MyModule.IntegrationEvents.csproj
└── tests/
    └── Services/
        └── MyModularService/
            └── Modules/
                └── MyModule/
                    β”œβ”€β”€ IntegrationTests/
                    β”‚   └── MyApp.MyModularService.MyModule.IntegrationTests.csproj
                    └── UnitTests/
                        └── MyApp.MyModularService.MyModule.UnitTests.csproj

Database Structure

It is important to highlight that the database is tied to the service. Within a modular service, each module is represented by its own schema in the associated database. This approach provides isolation and organization without the need to create multiple separate databases.

On Module Independence

Just as services have no direct dependencies on each other, modules are also created as independent projects within the service.

Benefits of this decoupling:

  • Isolation: Each module can evolve independently, making maintenance and continuous improvement easier.
  • Organization: The application becomes truly modular, respecting bounded contexts and promoting good software architecture practices.
  • Flexibility: It allows adding, removing, or refactoring modules without directly affecting other modules of the service.
  • Testability: Each module can be tested in isolation, increasing the reliability and quality of the system.

With that, we conclude the module creation process. In the upcoming topics, we will look at how to structure the internal elements of a module, such as entities, value objects, enumerations, commands, queries, APIs, integrations, and much more.

An unhandled error has occurred. Reload πŸ—™