3
\$\begingroup\$

I'm currently all about separating things where it makes sense. We've come to conclusion that small, slim, only-what-really-need models are the only one accepeatable.

How about this scenario?

public class EmailModel
{
  public int Id {get; set;}
  public string Subject {get; set;}
  public string Body {get; set;}
  public List<AttachmentModel> Attachments {get; set;}
  ...
}

To create a new email, user makes HTTP POST request:

POST api/v1/emails
{
  "subject": "email subject",
  "body": "some email content .. "
}

There is additional api for atttachments ofcourse api/v1/emails/attachments with POST to upload attachment and GET to retrieve email attachments.

GET email returns something like this:

GET api/v1/emails/1
{
  "id": "1",
  "subject": "email subject",
  "body": "some email content .. "
  "attachments": []
}

Is it better to have two models then?

public class CreateEmailModel // for http post
{
  public string Subject {get; set;}
  public string Body {get; set;}
}
public class EmailModel : CreateEmailModel // for http get
{
  public int Id {get; set;}
  public List<AttachmentModel> Attachments {get; set;}
}

Or is this complicating things for no reason, but still when another developer sees the code it's immediately clear what the model does. No model property is unneccessary.

\$\endgroup\$
2
\$\begingroup\$

I would not over-complicate the matter by splitting it up in several models. But if you want to stick to the choice of splitting it up, I would define an abstract base class and create a Create and Read model.

Because in the current situation I find the naming not 100% clear. Certainly because EmailModel (which sounds generic/basic) derives from CreateEmail (which is specific). It should be the other way around, a specific class should inherit from a generic/basic class.

Here's a rough implementation:

public abstract class EmailModel
{
    public string Subject {get; set;}
    public string Body {get; set;}
}

public class CreateEmailModel : EmailModel { }

public class ReadEmailModel : EmailModel
{
    public int Id {get; set;}
    public List<AttachmentModel> Attachments {get; set;}
}
\$\endgroup\$
  • \$\begingroup\$ 1) The abstract CreateEmailModel does not have to exist altogether if EmailModel was made a concrete class. Type proliferation is unnecessary here, and therefore is bad. 2) I'd recommend to change the names slightly. The CreateEmailModel and ReadEmailModel are misleading. First of all, in English they read as actions, but model classes are supposed to be nouns. EmailWithoutId/UnsavedEmail and CompleteEmail/SavedEmail/Email would be more intuitive and descriptive. \$\endgroup\$ – Igor Soloydenko Sep 23 '17 at 6:12
  • \$\begingroup\$ @Abbas I was leaning to not splitting to two models as well (it just makes everything too complicated). Good point about naming models and creating EmailModel abstract class. \$\endgroup\$ – broadband Sep 25 '17 at 9:29
0
\$\begingroup\$

Depending on how robust you need the API to be, one method used is to create an API transformation layer.

So you'd have just the one, true model:

public class EmailModel
{
    public int Id {get; set;}
    public string Subject {get; set;}
    public string Body {get; set;}
    public List<AttachmentModel> Attachments {get; set;}
    ...
}

This model is what the POST verb operates on, since it's just setting a subset of the Model's properties.

And then a transformation for that model for GETs (or however many transformations you need for different uses of that model):

public static class EmailTransformation
{
    public static string Transform(EmailModel model)
    {
        StringBuilder output = new StringBuilder();

        // The opening JSON scaffolding.
        output.Append("{");

        // Add each field you want in the output.
        output.Append("\"id\": \""); 
        output.Append(model.Id); 
        output.Append("\""); 

        ...

        // The closing JSON scaffolding.
        output.Append("}");

        return output.ToString();
    }
}

Of course, you can use more robust methods of transforming the object to JSON, but a simple StringBuilder demonstrates the principle.

The benefits of this approach are:

  1. You can include as many models or transformations as you want to form composite responses. For instance, you could have a separate AttachmentTransformation to transform each AttachmentModel. At the extreme, you can expose these as optional relationships in the API, allowing the consumer to decide if they want those relationships included or not.
  2. You create another abstraction between your public API and your internal structure. So, if you later decide to remove or add or rename a property in your models, your API doesn't have to change (as far as the consumers know). Or if you need to create a v2 of the API, you can easily add the version number to your transformation classes to separate the logic for each API version.

As an alternative, consider if you had a UserModel with a Password property. The API should never have to expose that property but it does always exist in the model. Splitting the UserModel into different classes based on the API verb makes it look like that Password property isn't always there (or you might have to jump through hoops to strip it out of child classes). With a transformation, it's explicit that the Password property is a part of the model but that it isn't supposed to be exposed by the API.

\$\endgroup\$
  • \$\begingroup\$ I think showing string builder even for idea demonstration purposes is not great. Manual transformations like that are super error prone, and novice developers looking at this sample may think, it's okay to follow that route. While the idea of customizable serializers is totally valid, StringBuilder based approach is almost pure evil. \$\endgroup\$ – Igor Soloydenko Sep 23 '17 at 6:05
  • \$\begingroup\$ I understand your reservations but I believe it's fine, in this instance. The question is about architecting models in relation to API verbs, not the transformation from a C# object to a JSON string. The OP even left that part out of his code, entirely. Given that he could use a standard serializer or a custom serializer, he could pull the string building out into a dedicated utility class, or there might even be an extant library dedicated to doing API transformations; I think those are just distracting implementation details. \$\endgroup\$ – Siegen Sep 24 '17 at 12:35
  • \$\begingroup\$ @Siegen I use Newtonsoft JSON for serialization objects to JSON and deserialization from JSON string to object. \$\endgroup\$ – broadband Sep 25 '17 at 9:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.