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 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.
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
.