39 votes

Conception DTO de ServiceStack Request

Je suis une .Net développeur a utilisé pour développer des applications web sur les Technologies Microsoft. Je suis en train de me renseigner pour comprendre le RESTE de l'approche pour les services web. Jusqu'à présent, je suis amoureuse de la ServiceStack cadre.

Mais parfois, je me retrouve à écrire des services dans un mode que je suis habitué avec WCF. J'ai donc une question qui me chiffonne.

J'ai 2 demande DTO est donc 2 services tels que:

[Route("/bookinglimit", "GET")]
[Authenticate]
public class GetBookingLimit : IReturn<GetBookingLimitResponse>
{
    public int Id { get; set; }
}
public class GetBookingLimitResponse
{
    public int Id { get; set; }
    public int ShiftId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Limit { get; set; }

    public ResponseStatus ResponseStatus { get; set; }
}

[Route("/bookinglimits", "GET")]
[Authenticate]
public class GetBookingLimits : IReturn<GetBookingLimitsResponse>
{      
    public DateTime Date { get; set; }
}
public class GetBookingLimitsResponse
{
    public List<GetBookingLimitResponse> BookingLimits { get; set; }
    public ResponseStatus ResponseStatus { get; set; }
}

Comme on le voit sur ces Demande DTO j'ai demande similaire DTO près de tous les services et cela semble ne pas SÉCHER.

J'ai essayé d'utiliser GetBookingLimitResponse de la classe dans une liste dans GetBookingLimitsResponse , pour cette raison, ResponseStatus à l'intérieur d' GetBookingLimitResponse classe est dublicated dans le cas où j'ai une erreur sur GetBookingLimits de service.

J'ai également implémentations de services pour ces demandes comme :

public class BookingLimitService : AppServiceBase
{
    public IValidator<AddBookingLimit> AddBookingLimitValidator { get; set; }

    public GetBookingLimitResponse Get(GetBookingLimit request)
    {
        BookingLimit bookingLimit = new BookingLimitRepository().Get(request.Id);
        return new GetBookingLimitResponse
        {
            Id = bookingLimit.Id,
            ShiftId = bookingLimit.ShiftId,
            Limit = bookingLimit.Limit,
            StartDate = bookingLimit.StartDate,
            EndDate = bookingLimit.EndDate,
        };
    }

    public GetBookingLimitsResponse Get(GetBookingLimits request)
    {
        List<BookingLimit> bookingLimits = new BookingLimitRepository().GetByRestaurantId(base.UserSession.RestaurantId);
        List<GetBookingLimitResponse> listResponse = new List<GetBookingLimitResponse>();

        foreach (BookingLimit bookingLimit in bookingLimits)
        {
            listResponse.Add(new GetBookingLimitResponse
                {
                    Id = bookingLimit.Id,
                    ShiftId = bookingLimit.ShiftId,
                    Limit = bookingLimit.Limit,
                    StartDate = bookingLimit.StartDate,
                    EndDate = bookingLimit.EndDate
                });
        }


        return new GetBookingLimitsResponse
        {
            BookingLimits = listResponse.Where(l => l.EndDate.ToShortDateString() == request.Date.ToShortDateString() && l.StartDate.ToShortDateString() == request.Date.ToShortDateString()).ToList()
        };
    }
}

Comme vous le voyez je tiens également à utiliser la Fonctionnalité de Validation ici, donc je dois écrire des classes de validation pour chaque demande DTO j'ai. Donc, j'ai un sentiment que je devrais garder mon numéro de service à la basse par le regroupement des services similaires en un seul service.

Mais la question est ici que surgit dans mon esprit que dois-je envoyer plus d'informations que le client en a besoin pour la demande ?

Je pense que ma façon de penser doit changer parce que je ne suis pas heureux avec code actuel qui j'ai écrit à penser comme un WCF gars.

Quelqu'un peut me montrer la direction à suivre.

88voto

mythz Points 54874

Pour vous donner une idée des différences que vous devez penser lors de la conception de services de messagerie en ServiceStack je vais vous donner quelques exemples de comparer WCF/WebApi vs ServiceStack son approche:

WCF vs ServiceStack Conception d'API

WCF vous encourage à penser à des services web comme normal C# appels de méthode, de l'e.g:

public interface IWcfCustomerService
{
    Customer GetCustomerById(int id);
    List<Customer> GetCustomerByIds(int[] id);
    Customer GetCustomerByUserName(string userName);
    List<Customer> GetCustomerByUserNames(string[] userNames);
    Customer GetCustomerByEmail(string email);
    List<Customer> GetCustomerByEmails(string[] emails);
}

C'est ce que le même contrat de Service pourrait ressembler dans ServiceStack avec la Nouvelle API:

public class Customers : IReturn<List<Customer>> 
{
   public int[] Ids { get; set; }
   public string[] UserNames { get; set; }
   public string[] Emails { get; set; }
}

Le concept le plus important à garder à l'esprit est que l'intégralité de la requête (aka Demande) est capturé dans le Message de Demande (c'est à dire Demande DTO) et non dans le serveur de signatures de méthode. L'évidence immédiate de l'adoption d'un message à base de modèle est que toute combinaison de ce qui précède appels RPC peuvent être atteints dans le 1 messages à distance, par un seul service de la mise en œuvre.

WebApi vs ServiceStack Conception d'API

De même, WebApi favorise une similaire en C#comme Api RPC qui WCF:

public class ProductsController : ApiController 
{
    public IEnumerable<Product> GetAllProducts() {
        return products;
    }

    public Product GetProductById(int id) {
        var product = products.FirstOrDefault((p) => p.Id == id);
        if (product == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return product;
    }

    public Product GetProductByName(string categoryName) {
        var product = products.FirstOrDefault((p) => p.Name == categoryName);
        if (product == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return product;
    }

    public IEnumerable<Product> GetProductsByCategory(string category) {
        return products.Where(p => string.Equals(p.Category, category,
                StringComparison.OrdinalIgnoreCase));
    }

    public IEnumerable<Product> GetProductsByPriceGreaterThan(decimal price) {
        return products.Where((p) => p.Price > price);
    }
}

ServiceStack Message API Basée sur le Design

Alors que ServiceStack vous encourage à conserver un Message de Conception basée sur:

public class FindProducts : IReturn<List<Product>> {
    public string Category { get; set; }
    public decimal? PriceGreaterThan { get; set; }
}

public class GetProduct : IReturn<Product> {
    public int? Id { get; set; }
    public string Name { get; set; }
}

public class ProductsService : Service 
{
    public object Get(FindProducts request) {
        var ret = products.AsQueryable();
        if (request.Category != null)
            ret = ret.Where(x => x.Category == request.Category);
        if (request.PriceGreaterThan.HasValue)
            ret = ret.Where(x => x.Price > request.PriceGreaterThan.Value);            
        return ret;
    }

    public Product Get(GetProduct request) {
        var product = request.Id.HasValue
            ? products.FirstOrDefault(x => x.Id == request.Id.Value)
            : products.FirstOrDefault(x => x.Name == request.Name);

        if (product == null)
            throw new HttpError(HttpStatusCode.NotFound, "Product does not exist");

        return product;
    }
}

De nouveau capturer l'essence de la Demande dans la Demande de DTO. Le message de base de la conception est également capable de se condenser 5 RPC distinctes WebAPI services en 2 basé sur le message ServiceStack ceux.

Groupe par l'Appel de la Sémantique et des Types de Réponse

C'est regroupées dans 2 services différents dans cet exemple basé sur un Appel à la Sémantique et les Types de réponses:

Tous les biens de chaque Demande DTO a la même sémantique que c'est pour FindProducts chaque propriété agit comme un Filtre (par exemple, une ET), tandis que, en GetProduct il agit comme un combinateur (par exemple, un OU). Les Services de revenir IEnumerable<Product> et Product types de retour, qui nécessitera un traitement différent dans l'appel-sites de Tapé dans l'Api.

Dans WCF / WebAPI (et d'autres services RPC cadres) chaque fois que vous avez un client spécifique à condition que vous ajoutez un nouveau Serveur de signature sur le contrôleur qui correspond à cette demande. Dans ServiceStack message de l'approche fondée sur les cependant, vous devriez toujours penser à où cette fonction appartient et que vous êtes en mesure d'améliorer les services existants. Vous devez également penser à comment vous pouvez soutenir le client-spécifiques aux besoins de façon générique , de sorte que le même service peut bénéficier à d'autres potentiels futurs cas d'utilisation.

Re-factoring GetBooking Limites des services

Avec les informations ci-dessus, nous pouvons commencer à re-factoring vos services. Puisque vous avez 2 services différents qui renvoient à des résultats différents par exemple, GetBookingLimit retourne 1 point et GetBookingLimits retours nombreux, ils doivent être conservés dans des services différents.

Distinguer les Opérations de Service vs Types

Vous devez cependant avoir une séparation nette entre vos Opérations de Service (par exemple la Requête DTO) qui est unique pour chaque service, et est utilisé pour capturer les Services à la demande, et la DTO types de retour. Demande Otd sont généralement des actions, de sorte qu'ils sont des verbes, tandis que les DTO sont les types d'entités de données/-les contenants de sorte qu'ils sont des noms.

Retour générique réponses

Dans la Nouvelle API, ServiceStack des réponses n'est plus nécessaire de ResponseStatus biens, car si elle n'existe pas pour le générique de l' ErrorResponse DTO seront jetés et sérialisé sur le client à la place. Cela vous permet d'avoir vos Réponses contiennent ResponseStatus propriétés. Cela dit je re-facteur du contrat de vos nouveaux services pour:

[Route("/bookinglimits/{Id}")]
public class GetBookingLimit : IReturn<BookingLimit>
{
    public int Id { get; set; }
}

public class BookingLimit
{
    public int Id { get; set; }
    public int ShiftId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Limit { get; set; }
}

[Route("/bookinglimits/search")]
public class FindBookingLimits : IReturn<List<BookingLimit>>
{      
    public DateTime BookedAfter { get; set; }
}

Pour les requêtes GET, j'ai tendance à les laisser sortir de la définition de la Route quand ils ne sont pas ambiguë, car c'est moins de code.

Garder une cohérence de la Nomenclature

Nous vous conseillons de réserver le mot d'Obtenir sur les services de requête sur unique ou de Clés Primaires des champs, c'est à dire quand une valeur donnée correspond à un domaine (par exemple, Id) il ne Reçoit 1 résultat. Pour les services de recherche qui agit comme un filtre et renvoie plusieurs résultats correspondant qui tombe à l'intérieur d'une plage, j'utilise soit le Trouver ou Rechercher des verbes pour indiquer que c'est le cas.

Objectif pour l'auto-description de Contrats de Service

Aussi, essayez d'être descriptive avec chacun de vos noms de champ, ces propriétés font partie de votre API publique et devrait être auto-descriptif de ce qu'il fait. E. g. Juste en regardant le Contrat de Service (par exemple la Requête DTO) nous n'avons aucune idée de ce que la Date n', j'ai supposé BookedAfter, mais il pourrait aussi avoir été BookedBefore ou BookedOn si elle ne retourne que les réservations effectuées à ce Jour.

L'avantage de ce qui est maintenant l'appel-les sites de votre tapé .NET les clients deviennent plus faciles à lire:

Product product = client.Get(new GetProduct { Id = 1 });

List<Product> results = client.Get(
    new FindBookingLimits { BookedAfter = DateTime.Today });

Service de la mise en œuvre

J'ai supprimé l' [Authenticate] de l'attribut de votre Demande Otd depuis la place, vous pouvez simplement spécifier qu'une fois le Service de la mise en œuvre, qui ressemble maintenant à:

[Authenticate]
public class BookingLimitService : AppServiceBase 
{ 
    public BookingLimit Get(GetBookingLimit request) { ... }

    public List<BookingLimit> Get(FindBookingLimits request) { ... }
}

Gestion des erreurs et de Validation

Pour info sur comment ajouter de validation vous avez l'option de juste jeter C# exceptions et appliquer vos propres personnalisations pour eux, sinon vous avez la possibilité d'utiliser le haut- Fluent de Validation , mais vous n'avez pas besoin de les injecter dans votre service comme vous pouvez le fil avec une seule ligne dans votre AppHost, e.g:

container.RegisterValidators(typeof(CreateBookingValidator).Assembly);

Les validateurs sont no-touch et envahissantes libre ce qui signifie que vous pouvez les ajouter à l'aide d'une approche à plusieurs niveaux et de les maintenir sans modification de la mise en œuvre des services ou de la DTO des classes. Depuis il a besoin d'une classe supplémentaire, je voudrais les utiliser uniquement sur les opérations avec des effets secondaires (par exemple POST/PUT) que Se " ont tendance à avoir peu de validation et en jetant un C# Exception nécessite moins de la chaudière de la plaque. Donc un exemple d'un programme de validation, vous pourriez avoir est lors de la première création d'une réservation:

public class CreateBookingValidator : AbstractValidator<CreateBooking>
{
    public CreateBookingValidator()
    {
        RuleFor(r => r.StartDate).NotEmpty();
        RuleFor(r => r.ShiftId).GreaterThan(0);
        RuleFor(r => r.Limit).GreaterThan(0);
    }
}

Selon le cas d'utilisation au lieu d'avoir un CreateBooking et UpdateBooking Otd je re-utiliser la même Demande DTO pour les deux dans ce cas, je voudrais nommer StoreBooking.

10voto

paaschpa Points 4169

La "Reponse Otd" semblent inutiles puisque ResponseStatus de propriété est plus nécessaire.. Cependant, je pense que vous pouvez toujours besoin d'une correspondance de classe de Réponse si vous utilisez du SAVON. Si vous supprimez la Réponse Otd vous n'avez plus besoin de pousser BookLimit en Réponse objets. Aussi, ServiceStack de TranslateTo() pourrait aider.

Ci-dessous est la façon dont je voudrais essayer de simplifier ce que vous avez posté...YMMV.

Faire un DTO pour BookingLimit - Ce sera la représentation de BookingLimit à tous les autres systèmes.

public class BookingLimitDto
{
    public int Id { get; set; }
    public int ShiftId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Limit { get; set; }
}

Les demandes et les Dto sont très important

[Route("/bookinglimit", "GET")]
[Authenticate]
public class GetBookingLimit : IReturn<BookingLimitDto>
{
    public int Id { get; set; }
}

[Route("/bookinglimits", "GET")]
[Authenticate]
public class GetBookingLimits : IReturn<List<BookingLimitDto>>
{
    public DateTime Date { get; set; }
}

Plus de retour Reponse objets...juste le BookingLimitDto

public class BookingLimitService : AppServiceBase 
{ 
    public IValidator AddBookingLimitValidator { get; set; }

    public BookingLimitDto Get(GetBookingLimit request)
    {
        BookingLimitDto bookingLimit = new BookingLimitRepository().Get(request.Id);
        //May need to bookingLimit.TranslateTo<BookingLimitDto>() if BookingLimitRepository can't return BookingLimitDto

        return bookingLimit; 
    }

    public List<BookingLimitDto> Get(GetBookingLimits request)
    {
        List<BookingLimitDto> bookingLimits = new BookingLimitRepository().GetByRestaurantId(base.UserSession.RestaurantId);
        return
            bookingLimits.Where(
                l =>
                l.EndDate.ToShortDateString() == request.Date.ToShortDateString() &&
                l.StartDate.ToShortDateString() == request.Date.ToShortDateString()).ToList();
    }
} 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X