ServiceStack Request and Response Objects

2019-03-31 15:15发布

问题:

Is it ok (read good practice) to re-use POCO's for the request and response DTO's. Our POCO's are lightweight (ORM Lite) with only properties and some decorating attributes.

Or, should I create other objects for the request and/or response?

Thanks,

回答1:

I would say it depends on your system, and ultimately how big and complex it is now, and has the potential to become.

The ServiceStack documentation doesn't specify which design pattern you should use. Ultimately it provides the flexibility for separating the database model POCO's from the DTOs, but it also provides support for their re-use.

When using OrmLite:

OrmLite was designed so that you could re-use your data model POCOs as your request and response DTOs. As noted from the ServiceStack documentation, this was an intentional design aim of the framework:

The POCOs used in Micro ORMS are particularly well suited for re-using as DTOs since they don't contain any circular references that the Heavy ORMs have (e.g. EF). OrmLite goes 1-step further and borrows pages from NoSQL's playbook where any complex property e.g. List is transparently blobbed in a schema-less text field, promoting the design of frictionless Pure POCOS that are uninhibited by RDBMS concerns.

Consideration:

If you do opt to re-use your POCOs, because it is supported, you should be aware that there are situations where it will be smarter to use separate request and response DTOs.

In many cases these POCO data models already make good DTOs and can be returned directly instead of mapping to domain-specific DTOs.

^ Not all cases. Sometimes the difficulty of choosing your design pattern is foreseeing the cases where it may not be suitable for re-use. So hopefully a scenario will help illustrate a potential problem.

Scenario:

  • You have a system where users can register for your service.
  • You, as the administrator, have the ability to list users of your service.

If you take the OrmLite POCO re-use approach, then we may have this User POCO:

public class User
{
    [PrimaryKey, AutoIncrement, Alias("Id")]
    public int UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Salt { get; set; }
    public bool Enabled { get; set; }
}

When you make your Create User request you populate Username and Password of your User POCO as your request to the server.

We can't just push this POCO into the database because:

  • The password in the Password field will be plain text. We are good programmers, and security is important, so we need to create a salt which we add to the Salt property, and hash Password with the salt and update the Password field. OK, that's not a major problem, a few lines of code will sort that before the insert.

  • The client may have set a UserId, but for create this wasn't required and will cause our database query to fail the insert. So we have to default this value before inserting into the database.

  • The Enabled property may have been passed with the request. What if somebody has set this? We only wanted the deal with Username and Password, but now we have to consider other fields that would effect the database insert. Similarly they could have set the Salt (though this wouldn't be a problem because we would be overriding the value anyway.) So now you have added validation to do.

But now consider when we come to returning a List<User>.

If you re-use the POCO as your response type, there are a lot of fields that you don't want exposed back to the client. It wouldn't be smart to do:

return Db.Select<User>();

Because you don't have a tight purpose built response for listing Users, the Password hash and the Salt would need to be removed in the logic to prevent it being serialised out in the response.

Consider also that during the registration of a user, that as part of the create request we want to ask if we should send a welcome email. So we would update the POCO:

public class User
{
    // Properties as before
    ...
    [Ignore] // This isn't a database field
    public bool SendWelcomeEmail { get; set; }
}

We now have the additional property that is only useful in the user creation process. If you use the User POCO over and over again, you will find over time you are adding more and more properties that don't apply to certain contexts.

When we return the list of users, for example, there is now an optional property of SendWelcomeEmail that could be populated - it just doesn't make sense. It can then be difficult to maintain the code.

A key thing to remember is that when sharing a POCO object such that it is used as both a request and response object: Properties that you send as a response will be exposed in a request. You will have to do more validation on requests, ultimately the sharing of the POCO may not save effort.

In this scenario wouldn't it be far easier to do:

public class CreateUserRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
    public bool SendWelcomeEmail { get; set; }
}

public class UserResponse
{
    public int UserId { get; set; }
    public string Username { get; set; }
    public bool Enabled { get; set; }
}

public class User
{
    [PrimaryKey, AutoIncrement, Alias("Id")]
    public int UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Salt { get; set; }
    public bool Enabled { get; set; }
}

We know now when we create a request (CreateUserRequest) that we don't have to consider UserId, Salt or Enabled.

When returning a list of users it's now List<UserResponse> and there is no chance the client will see any properties we don't want them to see.

It's clear to other people looking at the code, the required properties for requests, and what will be exposed in response.

Summary:

Sorry, it's a really long answer, but I think this addresses an aspect of sharing POCOs that some people miss, or fail to grasp initially, I was one of them.

  • Yes you can re-use POCOs for requests and response.
  • The documentation says it's OK to do so. In fact it is by design.
  • In many cases it will be fine to re-use.
  • There are cases where it's not suitable. (My scenario tries to show this, but you'll find as you develop your own real situations.)
  • Consider how many additional properties may be exposed because your shared POCO tries to support multiple actions, and how much extra validation work may be required.
  • Ultimately it's about what you are comfortable maintaining.

Hope this helps.



回答2:

We have other approach, and my answer is opinionated.

Because we work not only with C# clients, but mainly with JavaScript clients.

The request and response DTO's, the routes and the data entities, are negotiated between

the customer and the front-end analyst. They are part of the specs in a detailed form.

Even if "customer", in some cases, is our product UI.

These DTO's don't change without important reason and can be reusable in both sides.

But the objects in the data layer, can be the same or partial class or different,

They can be changed internally, including sensitive or workflow information,

but they have to be compatible with the specification of the API.

We start with the API first , not the database or ORM.

           Person  { ... }

           Address  { ... }

           ResponceDTO
           { 
             public bool success {get; set;}
             public string message {get; set;}
             public Person  person {get; set;}  
             public List<Address> addresses {get; set;}  
    //customer can use the Person & Address, which can be the same or different
   //with the ones in the data-layer. But we have defined these POCO's  in the specs.                           
            }
           RequestDTO
           { 
             public int Id {get; set;}
             public FilteByAge {get; set;}
            public FilteByZipCode {get; set;}
            }               
           UpdatePersonRequest
           { 
             public int Id {get; set;}
             public bool IsNew {get; set;}
             public Person  person {get; set;} 
             public List<Address> addresses {get; set;}   
            }                  

We don't expose only Request or Response DTOs.

The Person and Address are negotiated with the customer and are referenced in the API specs.

They can be the same or partial or different from the data-layer internal implementation.

Customer will use them to their application or web site, or mobile.

but the important is that we design and negotiate first the API interface.

We use also often the requestDTO as parameter to the business layer function,

which returns the response object or collection.

By this way the service code is a thin wrapper in front of the business layer.

        ResponseDTO  Get(RequestDTO  request)
         {
                      return GetPersonData(request);
          }

Also from the ServiceStack wiki , the API-First development approach



回答3:

This will not be a problem given you are OK with exposing the structure of your data objects (if this is a publicly consumed API). Otherwise, Restsharp is made to be used with simple POCOs :)



回答4:

I think it all depends on how you're using your DTO's, and how you want to balance re-usability of code over readability. If both your requests and responses both utilize a majority of properties on your DTO's, then you'll be getting a lot of re-usability without really lowering readability. If, for instance, your request object has 10 properties (or vice-versa), but your response only needs 1 of them, someone could make an argument that it's easier to understand/read if your response object only had that 1 property on it.

In summary, good practice is just clean code. You have to evaluate your particular use case on whether or not your code is easy to use and read. Another way to think of it, is to write code for the next person who will read it, even if that person is you.