Quote of the Day

more Quotes

Categories

Buy me a coffee

Should You Use Shared Models or Separate Sets of Models?

Published April 14, 2024 in Software Development - 0 Comments

In the past, I’ve worked on software projects where there were many model classes with very similar properties. I often questioned the necessity of having multiple models that appeared almost identical, as this approach seemed to introduce code duplication and unnecessary complexity. On the other hand, I’ve also contributed to projects where a few large, multipurpose model classes were used. These projects often suffered from inflexibility and the potential for widespread issues whenever modifications were required, as changes to one model could impact multiple components. In software development, the principle of ‘separation of concerns‘ is important for creating maintainable and scalable applications. Personally, I’ve come to favor using separate models to adhere to this principle. In this post, I’ll discuss the using shared versus separate models through a recent project example—a Blazor web application I worked on for building chatbots.

Blazor is a Microsoft technology that enables developers to use C# to build rich user interfaces and web APIs within the same project. One advantage of this approach is the ability to share code between the front end and the back end. In a Blazor server application, for instance, a web project containing Razor pages can utilize services, data transfer objects, and models from other projects. Consider, for example, the following POCO class, which models a knowledge base: a conceptual container for documents that can be fed into a large language model.

 public record KnowledgeBaseModel
    {
        public string? Name { get; set; }
        public string? Description { get; set; }
    }

Both the infrastructure and the web layers utilize this model. The infrastructure layer uses the model for JSON serialization when calling the API to create the knowledge base. In the web layer, the model helps display the form where users can input the required information, such as the name and description of the knowledge base; other fields are set by default.

Add Knowledge Base form.

However, sharing the model across different layers, as described, increases the coupling between the infrastructure and the web layers, potentially complicating maintenance and scalability. For instance, the app provides a user a form to edit other fields besides the name and description, specifically the chunk size and chunk overlap settings, which are managed by separate API endpoints.

If we were to reuse the KnowledgeBaseModel class for these settings, we would have to amend the model to add the properties for ChunkSize and ChunkOverlap as well as the id of the knowledge base for updating the settings. However, in the form to create the knowledge base, the id is not applicable because the knowledge base has not been created. Therefore, reusing the same model for both creating and editing the knowledge base, as well as for the corresponding UI forms, would result in tight coupling. This setup means that changes to the model could affect multiple components across the application.

A better design involves using separate models for API calls and forms to reduce coupling and enhance maintainability. For instance, we could introduce a dedicated model specifically for the view to display the edit form.

  /// <summary>
  /// Represents a form for editing a knowledge base.
  /// </summary>
  public record EditKnowledgeBaseViewModel
  {
      // Name of the knowledge base.
      public string? Name { get; set; }
      // Description of the knowledge base.
      public string? Description { get; set; }

      public ChunkSizeSettingsViewModel ChunkSizeSettings {get; set;} = new ChunkSizeSettingsViewModel();
  }

public record ChunkSizeSettingsViewModel
{
    public int ChunkSize { get; set; }
    public int ChunkOverlap { get; set; }
}

Similarly, we could have separate models for the API call to create the knowledge base and edit the chunk size and chunk overlap.

 public record UpdateKnowledgeBaseSettingsModel
 {
     public int KnowledgeBaseId { get; set; }
     public int ChunkSize { get; set; }
     public int ChunkOverlap { get; set; }
 }

 public record CreateKnowledgeBaseModel 
 {
     public string? Name { get; set; }
     public string? Description { get; set; }
 }

Having separate models for the forms and API calls decreases the coupling between these components, making the design more modular and flexible. For example, if the API changes its implementation to allow the user to pass settings and properties of the knowledge base in the same JSON body, the UI does not need to undergo any modifications. This separation ensures that frontend updates are minimal and only necessary when there are changes that directly affect user interactions, thereby enhancing the maintainability of the system.

However, using separate models often require the conversion or transfer of fields between these models. For instance, the edit form might retrieve the name and description of the knowledge base from one model, while getting the chunk size and chunk overlap from another. Libraries like AutoMapper are specifically designed to facilitate this type of model conversion efficiently. In my application, I employ a separate package dedicated to managing these conversions, leveraging AutoMapper to map data between different models.

For example, the below snippets demonstrate how we can use AutoMapper to transfer fields from the KnowledgeBaseModel to the EditKnowledgeBaseViewModel. Both models share the same fields for ‘name’ and ‘description’. Using AutoMapper, we can automatically map these fields without writing additional boilerplate code,

// AutoMapper configuration
configuration.CreateMap<KnowledgeBaseModel, EditKnowledgeBaseViewModel>();

In conclusion, the decision between using a shared model versus separate models often depends on the complexity of the project as well as personal or team preferences. A practical approach might be to start with a single model and refactor as soon as complications arise or when special setups or workarounds become necessary.

No comments yet