- Mastering ServiceStack
- Andreas Niedermair
- 682字
- 2021-07-09 21:27:14
A message-based service
If you have previously used Web API or Windows Communication Framework (WCF) you will find yourself in the habit of writing service methods specialized for only one scenario.
A typical interface to search through Task
instances would be something, like the following:
public class Task { public int Id { get; set; } public string Title { get; set; } public int UserId { get; set; } } interface IService { Task GetTaskById(int id); Task[] GetAllTasks(); Task[] GetTasksById(int[] ids); Task[] GetTasksForUserId(int userId); Task[] GetTasksByTitle(string title); Task[] GetTasksByTitleForUserId(string title, int userId); }
There is basically a separate and specialized method for each search option.
In contrast, according to the message pattern, this would be implemented as follows:
public class FindTasks : ServiceStack.IReturn<Task[]> { public int[] Ids { get; set; } public int[] UserIds { get; set; } public string Title { get; set; } }
Note
Additionally, to the basic definition of the message, ServiceStack.IReturn<T>
is already used here. There is no need whatsoever to implement this interface, but doing so for example gives you the possibility to deviate from the naming convention of ResponseDTO class names for the metadata page, and defines the return type on service clients Send
methods.
This combines the various search options into one message, which makes the following benefits obvious:
- Less distribution of logic
- Less maintenance due to less code duplication in the long run
- Easily add more functionality by introducing new properties in the message without adapting to existing usages that gives you a straightforward approach to various versions
- Less friction with caching, as the instances can be used to generate a cache key
- Easy to serialize and log
- When immutable, it's perfect for concurrency and multithreaded scenarios
To show these benefits in action, let's contrast the implementations, which are by no means optimized or perfectly well implemented:
public class Service : IService { Task[] _tasks = new [] { new Task { Id = 1, Title = "Task 1", UserId = 1 }, new Task { Id = 2, Title = "Task 2", UserId = 2 }, new Task { Id = 3, Title = "Task 3", UserId = 3 } }; public Task GetTaskById(int id) { return this._tasks.FirstOrDefault(arg => arg.Id == id); } public Task[] GetAllTasks() { return this._tasks; } public Task[] GetTasksById(int[] ids) { return this._tasks.Where(arg => ids.Contains(arg.Id)).ToArray(); } public Task[] GetTasksForUserId(int[] userIds) { return this._tasks.Where(arg => userIds.Contains(arg.UserId).ToArray(); } public Task[] GetTasksByTitle(string title) { return this._tasks.Where(arg => arg.Title.Contains(title)).ToArray(); } public Task[] GetTasksByTitleForUserId(string title, int userId) { return this._tasks.Where(arg => arg.Title.Contains(title) && arg.UserId == userId).ToArray(); } }
This basic Service
class holds an array of Task
objects that are used in every method for the specific query. Then the matching excerpt of the array is returned.
In a message-based service it would look like:
public partial class TaskService : ServiceStack.IService, ServiceStack.IAny<FindTasks> { Task[] _tasks = new [] { new Task { Id = 1, Title = "Task 1", UserId = 1 }, new Task { Id = 2, Title = "Task 2", UserId = 2 }, new Task { Id = 3, Title = "Task 3", UserId = 3 } }; public object Any(FindTasks request) { // we could generate a hash of the request and query // against a cache var tasks = this._tasks.AsQueryable(); if (request.Ids != null) { tasks = tasks.Where(arg => request.Ids.Contains(arg.Id)); } if (request.UserIds != null) { tasks = tasks.Where(arg => request.UserIds.Contains(arg.UserId)); } if (request.Title != null) { tasks = tasks.Where(arg => arg.Title.Contains(title)); } // here is room to implement more clauses return tasks; } }
The implementation of the actual endpoint is straightforward, just apply each filter prior to checking against null and return a matching excerpt.
Note
The added ServiceStack.IAny<T>
naturally forces an implementation of the request in the TaskService
class. You can still add your operation to the service manually, but I strongly advise you to follow the New API outline available at https://github.com/ServiceStack/ServiceStack/wiki/New-API.
This implementation can be easily connected to the following web page. It once again shows the power of the Code-First approach as it binds to the following interface with ease:

- .NET之美:.NET關(guān)鍵技術(shù)深入解析
- Learning Cython Programming(Second Edition)
- Apache Spark 2 for Beginners
- Learning Python Design Patterns(Second Edition)
- 你必須知道的204個(gè)Visual C++開發(fā)問題
- Python Data Analysis(Second Edition)
- PhoneGap:Beginner's Guide(Third Edition)
- UML 基礎(chǔ)與 Rose 建模案例(第3版)
- 劍指大數(shù)據(jù):企業(yè)級(jí)數(shù)據(jù)倉庫項(xiàng)目實(shí)戰(zhàn)(在線教育版)
- Python從入門到精通
- Access 2010中文版項(xiàng)目教程
- PHP 7從零基礎(chǔ)到項(xiàng)目實(shí)戰(zhàn)
- Python網(wǎng)絡(luò)爬蟲實(shí)例教程(視頻講解版)
- Neo4j 3.x入門經(jīng)典
- 你真的會(huì)寫代碼嗎