鍵.png)
ASP.NET Web API快速入門介紹
首先,定義一個ISimplifiedDataAccessor
接口,這個接口被放在了一個獨(dú)立的包(.NET中的Assembly)Stickers.Common
下,這個接口定義了一套CRUD的基本方法,在另一個獨(dú)立的包Stickers.DataAccess.InMemory
中,有一個實(shí)現(xiàn)了該接口的類:InMemoryDataAccessor
,它包含了一個IEntity
實(shí)體的列表數(shù)據(jù)結(jié)構(gòu),然后基于這個列表,實(shí)現(xiàn)了ISimplifiedDataAccessor
下的所有方法。而Stickers.WebApi
中的API控制器StickersController
則依賴ISimplifiedDataAccessor
接口,并由ASP.NET Core的依賴注入框架將InMemoryDataAccessor
的實(shí)例注入到控制器中。
為了構(gòu)圖美觀,類圖中所有方法的參數(shù)和返回類型都進(jìn)行了簡化,在案例的代碼中,各個方法的參數(shù)和返回類型都比圖中所示稍許復(fù)雜一些。
這里我們引入了IEntity
接口,所有能夠通過SDAC進(jìn)行數(shù)據(jù)訪問的數(shù)據(jù)對象,都需要實(shí)現(xiàn)這個接口。引入該接口的一個重要目的是為了實(shí)現(xiàn)泛型約束,以便可以在ISimplifiedDataAccessor
接口上明確指定什么樣的對象才可以被用于數(shù)據(jù)訪問。另外,這里還引入了一個泛型類型:Paginated<TEntity>
類型,它可以包含分頁信息,并且其中的Items
屬性保存的是某一頁的數(shù)據(jù)(頁碼由PageIndex
屬性指定),因?yàn)樵?code>StickersController控制器中,我們大概率會需要實(shí)現(xiàn)能夠支持分頁的“貼紙”查詢功能。
限于篇幅,就不對InMemoryDataAccessor
中的每個方法的具體實(shí)現(xiàn)進(jìn)行介紹了,有興趣的話可以打開本文最后貼出的源代碼鏈接,直接打開代碼閱讀。這里著重解讀一下GetPaginatedEntitiesAsync
方法的代碼:
public Task<Paginated<TEntity>> GetPaginatedEntitiesAsync<TEntity, TField>(
Expression<Func<TEntity, TField>> orderByExpression, bool sortAscending = true, int pageSize = 25,
int pageNumber = 0, Expression<Func<TEntity, bool>>? filterExpression = null,
CancellationToken cancellationToken = default) where TEntity : class, IEntity
{
var resultSet = filterExpression is not null
? _entities.Cast<TEntity>().Where(filterExpression.Compile())
: _entities.Cast<TEntity>();
var enumerableResultSet = resultSet.ToList();
var totalCount = enumerableResultSet.Count;
var orderedResultSet = sortAscending
? enumerableResultSet.OrderBy(orderByExpression.Compile())
: enumerableResultSet.OrderByDescending(orderByExpression.Compile());
return Task.FromResult(new Paginated<TEntity>
{
Items = orderedResultSet.Skip(pageNumber * pageSize).Take(pageSize).ToList(),
PageIndex = pageNumber,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (totalCount + pageSize - 1) / pageSize
});
}
這個方法的目的就是為了返回某一頁的實(shí)體數(shù)據(jù),首先分頁是需要基于排序的,因此,orderByExpression
參數(shù)通過Lambda表達(dá)式來指定排序的字段;sortAscending
很好理解,它指定是否按升序排序;pageSize
和pageNumber
指定分頁時每頁的數(shù)據(jù)記錄條數(shù)以及需要返回的數(shù)據(jù)頁碼;通過filterExpression
Lambda表達(dá)式參數(shù),還可以指定查詢過濾條件,比如,只返回“創(chuàng)建日期”大于某一天的數(shù)據(jù)。在InMemoryDataAccessor
中,都是直接對列表數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作,所以這個函數(shù)的實(shí)現(xiàn)還是比較簡單易懂的:如果filterExpression
有定義,則首先執(zhí)行過濾操作,然后再進(jìn)行排序,并構(gòu)建Paginated<TEntity>對象作為函數(shù)的返回值。在下一篇文章介紹PostgreSQL數(shù)據(jù)訪問的實(shí)現(xiàn)時,我們還會看到這個函數(shù)的另一個不同的實(shí)現(xiàn)。
在接口定義上,GetPaginatedEntitiesAsync是一個異步方法,所以,我們應(yīng)該盡可能地傳入CancellationToken對象,以便在該方法中能夠支持取消操作。
現(xiàn)在我們已經(jīng)有了數(shù)據(jù)訪問層,就可以開始實(shí)現(xiàn)Sticker微服務(wù)的RESTful API了。
我們是使用ASP.NET Core Web API創(chuàng)建的StickersController控制器,所以也會默認(rèn)使用RESTful來實(shí)現(xiàn)微服務(wù)的API,RESTful API基于HTTP協(xié)議,是目前微服務(wù)間通信使用最為廣泛的協(xié)議之一,由于它主要基于JSON數(shù)據(jù)格式,因此對前端開發(fā)和實(shí)現(xiàn)也是特別友好。RESTful下對于被訪問的數(shù)據(jù)統(tǒng)一看成資源,是資源就有地址、所支持的訪問方式等屬性,不過這里我們就不深入討論這些內(nèi)容了,重點(diǎn)講一下StickersController實(shí)現(xiàn)的幾個要點(diǎn)。
熟悉ASP.NET Core Web API開發(fā)的讀者,對于如何注入一個Service應(yīng)該是非常熟悉的,這里就簡單介紹下吧。在Stickers.Api
項(xiàng)目的Program.cs
文件里,直接加入下面這行代碼即可,注意加之前,先向項(xiàng)目添加對Stickers.DataAccess.InMemory
項(xiàng)目的引用:
builder.Services.AddSingleton<ISimplifiedDataAccessor, InMemoryDataAccessor>();
在這里,我將InMemoryDataAccessor
注冊為單例實(shí)例,雖然它是一個有狀態(tài)的對象,但使用它的目的也僅僅是讓整個應(yīng)用程序能夠運(yùn)行起來,后面是會用PostgreSQL進(jìn)行替換的(PostgreSQL的數(shù)據(jù)訪問層是無狀態(tài)的,因此在這里使用單例是合理的),所以在這里并不需要糾結(jié)它本身的實(shí)現(xiàn)是否合理、在單例下是否是線程安全。高內(nèi)聚低耦合的設(shè)計(jì)原則,讓問題變得更為簡單。
現(xiàn)在將Stickers.Api
項(xiàng)目下的WeatherForecastController
刪掉,然后新加一個Controller,命名為StickersController
,基本代碼結(jié)構(gòu)如下:
namespace Stickers.WebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class StickersController(ISimplifiedDataAccessor dac) : ControllerBase
{
// 其它代碼暫時省略
}
于是就可以在StickersController
控制器中,通過dac
實(shí)例來訪問數(shù)據(jù)存儲了。
控制器代碼的可測試性:由于StickersController僅依賴ISimplifiedDataAccessor接口,因此,在進(jìn)行單元測試時,完全可以通過Mock技術(shù),生成一個ISimplifiedDataAccessor的Mock對象,然后將其注入到StickersController中完成單元測試。
對于不同的RESTful API,在不同的情況下應(yīng)該返回合理的HTTP狀態(tài)碼,這是RESTful API開發(fā)的一種最佳實(shí)踐。尤其是在微服務(wù)架構(gòu)下,合理定義API的返回代碼,對于多服務(wù)集成是有好處的。我認(rèn)為可以遵循以下幾個原則:
以下面三個RESTful API方法為例:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Sticker))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetByIdAsync(int id)
{
var sticker = await dac.GetByIdAsync<Sticker>(id);
if (sticker is null) return NotFound($"Sticker with id {id} was not found.");
return Ok(sticker);
}
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(Sticker sticker)
{
var exists = await dac.ExistsAsync<Sticker>(s => s.Title == sticker.Title);
if (exists) return Conflict($"Sticker {sticker.Title} already exists.");
var id = await dac.AddAsync(sticker);
return CreatedAtAction(nameof(GetByIdAsync), new { id }, sticker);
}
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteByIdAsync(int id)
{
var result = await dac.RemoveByIdAsync<Sticker>(id);
if (!result) return NotFound($"Sticker with id {id} was not found.");
return NoContent();
}
這幾個方法都用到了Sticker
類,這個類代表了“貼紙”對象,它其實(shí)是一個領(lǐng)域?qū)ο螅缟衔乃f,目前我們僅將其用作數(shù)據(jù)傳輸對象,它的定義如下:
public class Sticker(string title, string content) : IEntity
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Title { get; set; } = title;
public string Content { get; set; } = content;
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;
public DateTime? ModifiedOn { get; set; }
}
Sticker
類實(shí)現(xiàn)了IEntity
接口,它是Stickers.WebApi
項(xiàng)目中的一個類,它被定義在了Stickers.WebApi
項(xiàng)目中,而不是定義在Stickers.Common
項(xiàng)目中,是因?yàn)閺腂ounded Context的劃分角度,它是Stickers.WebApi
項(xiàng)目的一個內(nèi)部業(yè)務(wù)對象,并不會被其它微服務(wù)所使用。
在CreateAsync
方法中,它會首先判斷相同標(biāo)題的“貼紙”是否存在,如果存在,則返回409;否則就直接創(chuàng)建貼紙,并返回201,同時帶上創(chuàng)建成功后“貼紙”資源的地址(CreatedAtAction
方法表示,資源創(chuàng)建成功,可以通過GetByIdAsync
方法所在的HTTP路徑,帶上新建“貼紙”資源的Id來訪問到該資源)。而在DeleteByIdAsync
方法中,API會直接嘗試刪除指定Id的“貼紙”,如果貼紙不存在,則返回404,否則就是成功刪除,返回204。
順便提一下在各個方法上所使用的ProducesResponseType
特性,一般我們可以將當(dāng)前API方法能夠返回的HTTP狀態(tài)碼都用這個特性(Attribute)標(biāo)注一下,以便Swagger能夠生成更為詳細(xì)的文檔:
ASP.NET Core Web API在一個Controller方法被調(diào)用前,是可以自動完成模型驗(yàn)證的。比如在上面的CreateAsync方法中,為什么我沒有對“貼紙”的標(biāo)題(Title)字段判空?而在這個API的返回狀態(tài)定義中,卻明確表示它有可能返回400?因?yàn)椋赟ticker類的Title屬性上,我使用了Required
和StringLength
這兩個特性:
[Required]
[StringLength(50)]
public string Title { get; set; } = title;
于是,在Sticker類被用于RESTful API的POST請求體(request body)時,ASP.NET Core Web API框架會自動根據(jù)這些特性來完成數(shù)據(jù)模型的驗(yàn)證,比如,在啟動程序后,執(zhí)行下面的命令:
$ curl -X POST http://localhost:5141/stickers \
-d '{"content": "hell world!"}' \
-H 'Content-Type: application/json' \
-v && echo
會得到下面的返回結(jié)果:
不僅如此,開發(fā)人員還可以擴(kuò)展System.ComponentModel.DataAnnotations.ValidationAttribute
來實(shí)現(xiàn)自定義的驗(yàn)證邏輯。
在開發(fā)RESTful API時,有個比較糾結(jié)的問題是,在修改資源時,是應(yīng)該用PUT還是PATCH?其實(shí)很簡單,PUT的定義是:使用數(shù)據(jù)相同的另一個資源來替換已有資源,而PATCH則是針對某個已有資源進(jìn)行修改。所以,單從修改對象的角度,PATCH要比PUT更高效,它不需要客戶端將需要修改的對象整個性地下載下來,修改之后又整個性地發(fā)送到后端進(jìn)行保存。于是,又產(chǎn)生另一個問題:服務(wù)端如何得知應(yīng)該修改資源的哪個屬性字段以及修改的方式是什么呢?一個比較直接的做法是,在服務(wù)端仍然接收來自客戶端由PATCH方法發(fā)送過來的Sticker對象,然后判斷這個對象中的每個字段的值是否有值,如果有,則表示客戶端希望修改這個字段,否則就跳過這個字段的修改。如果對象結(jié)構(gòu)比較簡單,這種做法可能也還行,但是如果對象中包含了大量屬性字段,或者它有一定的層次結(jié)構(gòu),那么這種做法就會顯得比較笨拙,不僅費(fèi)時費(fèi)力,而且容易出錯。
在RESTful API的實(shí)現(xiàn)中,一個比較好的做法是采用JSON Patch,它是一套國際標(biāo)準(zhǔn)(RFC6902),它定義了JSON文檔(JSON document)修改的基本格式和規(guī)范,而微軟的ASP.NET Core Web API原生支持JSON Patch。以下是StickersController控制器中使用JSON Patch的方法:
[HttpPatch("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdateStickerAsync(int id, [FromBody] JsonPatchDocument<Sticker>? patchDocument)
{
if (patchDocument is null) return BadRequest();
var sticker = await dac.GetByIdAsync<Sticker>(id);
if (sticker is null) return NotFound();
sticker.ModifiedOn = DateTime.UtcNow;
patchDocument.ApplyTo(sticker, ModelState);
if (!ModelState.IsValid) return BadRequest(ModelState);
await dac.UpdateAsync(id, sticker);
return Ok(sticker);
}
代碼邏輯很簡單,首先通過Id獲得“貼紙”對象,然后使用patchDocument.ApplyTo
方法,將客戶端的修改請求應(yīng)用到貼紙對象上,然后調(diào)用SDAC更新后端存儲中的數(shù)據(jù),最后返回修改后的貼紙對象。讓我們測試一下,首先新建一個貼紙:
$ curl -X POST http://localhost:5141/stickers \
> -H 'Content-Type: application/json' \
> -d '{"title": "Hello", "content": "Hello daxnet"}' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:5141 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:5141...
* Connected to localhost (::1) port 5141
> POST /stickers HTTP/1.1
> Host: localhost:5141
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 45
>
< HTTP/1.1 201 Created
< Content-Type: application/json; charset=utf-8
< Date: Sat, 12 Oct 2024 07:50:00 GMT
< Server: Kestrel
< Location: http://localhost:5141/stickers/1
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
{"id":1,"title":"Hello","content":"Hello daxnet","createdOn":"2024-10-12T07:50:00.9075598Z","modifiedOn":null}
然后,查看這個貼紙的數(shù)據(jù)是否正確:
$ curl http://localhost:5141/stickers/1 | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 110 0 110 0 0 9650 0 --:--:-- --:--:-- --:--:-- 10000
{
"id": 1,
"title": "Hello",
"content": "Hello daxnet",
"createdOn": "2024-10-12T07:50:00.9075598Z",
"modifiedOn": null
}
現(xiàn)在,使用PATCH方法,將content改為”Hello World”:
$ curl -X PATCH http://localhost:5141/stickers/1 \
> -H 'Content-Type: application/json-patch+json' \
> -d '[{"op": "replace", "path": "/content", "value": "Hello World"}]' -v && echo
* Host localhost:5141 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:5141...
* Connected to localhost (::1) port 5141
> PATCH /stickers/1 HTTP/1.1
> Host: localhost:5141
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/json-patch+json
> Content-Length: 63
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Sat, 12 Oct 2024 07:56:04 GMT
< Server: Kestrel
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
{"id":1,"title":"Hello","content":"Hello World","createdOn":"2024-10-12T07:50:00.9075598Z","modifiedOn":"2024-10-12T07:56:04.815507Z"}
注意上面命令中需要將Content-Type
設(shè)置為application/json-patch+json
,再執(zhí)行一次GET請求驗(yàn)證一下:
$ curl http://localhost:5141/stickers/1 | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 134 0 134 0 0 43819 0 --:--:-- --:--:-- --:--:-- 44666
{
"id": 1,
"title": "Hello",
"content": "Hello World",
"createdOn": "2024-10-12T07:50:00.9075598Z",
"modifiedOn": "2024-10-12T07:56:04.815507Z"
}
可以看到,content已經(jīng)被改為了Hello World,同時modifiedOn
字段也更新為了當(dāng)前資源被更改的UTC時間。
在服務(wù)端如果需要存儲時間信息,一般都應(yīng)該保存為UTC時間,或者本地時間+時區(qū)信息,這樣也能推斷出UTC時間,總之,在服務(wù)端,應(yīng)該以UTC時間作為標(biāo)準(zhǔn),這樣在不同時區(qū)的客戶端就可以根據(jù)服務(wù)端返回的UTC時間來計(jì)算并顯示本地時間,這樣就不會出現(xiàn)混亂。
?在ASP.NET Core中使用JSON Patch還需要引入Newtonsoft JSON Input Formatter,請按照微軟官方文檔的步驟進(jìn)行設(shè)置即可。
在前端應(yīng)用中,通常都可以支持用戶自定義的數(shù)據(jù)排序,也就是用戶可以自己決定是按數(shù)據(jù)的哪個字段以升序還是降序的順序進(jìn)行排序,然后基于這樣的排序完成分頁功能。其實(shí)實(shí)現(xiàn)的基本原理我已經(jīng)在《在ASP.NET Core Web API上動態(tài)構(gòu)建Lambda表達(dá)式實(shí)現(xiàn)指定字段的數(shù)據(jù)排序》一文中介紹過了,思路就是根據(jù)輸入的字段名構(gòu)建Lambda表達(dá)式,然后將Lambda表達(dá)式應(yīng)用到對象列表的OrderBy/OrderByDescending方法,或者是應(yīng)用到數(shù)據(jù)庫訪問組件上,以實(shí)現(xiàn)排序功能。下面就是StickersController控制器中的相關(guān)代碼:
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> GetStickersAsync(
[FromQuery(Name = "sort")] string? sortField = null,
[FromQuery(Name = "asc")] bool ascending = true,
[FromQuery(Name = "size")] int pageSize = 20,
[FromQuery(Name = "page")] int pageNumber = 0)
{
Expression<Func<Sticker, object>> sortExpression = s => s.Id;
if (sortField is not null) sortExpression = ConvertToExpression<Sticker, object>(sortField);
return Ok(
await dac.GetPaginatedEntitiesAsync(sortExpression, ascending, pageSize, pageNumber)
);
}
private static Expression<Func<TEntity, TProperty>> ConvertToExpression<TEntity, TProperty>(string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentNullException($"{nameof(propertyName)} cannot be null or empty.");
var propertyInfo = typeof(TEntity).GetProperty(propertyName);
if (propertyInfo is null) throw new ArgumentNullException($"Property {propertyName} is not defined.");
var parameterExpression = Expression.Parameter(typeof(TEntity), "p");
var memberExpression = Expression.Property(parameterExpression, propertyInfo);
if (propertyInfo.PropertyType.IsValueType)
return Expression.Lambda<Func<TEntity, TProperty>>(
Expression.Convert(memberExpression, typeof(object)),
parameterExpression);
return Expression.Lambda<Func<TEntity, TProperty>>(memberExpression, parameterExpression);
}
下面展示了根據(jù)Id字段進(jìn)行降序排列的命令行以及API調(diào)用輸出:
$ curl 'http://localhost:5141/stickers?sort=Id&asc=false&size=20&page=0' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 453 0 453 0 0 205k 0 --:--:-- --:--:-- --:--:-- 221k
{
"items": [
{
"id": 4,
"title": "c",
"content": "5",
"createdOn": "2024-10-12T11:55:10.8708238Z",
"modifiedOn": null
},
{
"id": 3,
"title": "d",
"content": "1",
"createdOn": "2024-10-12T11:54:37.9055791Z",
"modifiedOn": null
},
{
"id": 2,
"title": "b",
"content": "7",
"createdOn": "2024-10-12T11:54:32.4162609Z",
"modifiedOn": null
},
{
"id": 1,
"title": "a",
"content": "3",
"createdOn": "2024-10-12T11:54:23.3103948Z",
"modifiedOn": null
}
],
"pageIndex": 0,
"pageSize": 20,
"totalCount": 4,
"totalPages": 1
}
由于C#編程規(guī)定對于標(biāo)識符都使用Pascal命名規(guī)范,而ASP.NET Core Web API在產(chǎn)生URL時,是根據(jù)Controller和Action的名稱來決定的,所以,在路徑中都是默認(rèn)使用Pascal命名規(guī)范,也就是第一個字符是大寫字母。比如:http://localhost:5141/Stickers,其中“Stickers”的“S”就是大寫。然而,實(shí)際中大多數(shù)情況下,都希望能夠跟前端開發(fā)保持一致,也就是希望開頭第一個字母是小寫,比如像http://localhost:5141/stickers這樣。ASP.NET Core Web API提供了解決方案,在Program.cs
文件中加入如下代碼即可:
builder.Services.AddRouting(options =>
{
options.LowercaseUrls = true;
options.LowercaseQueryStrings = true;
});
在StickersController控制器中,我們使用了async/await來實(shí)現(xiàn)每個API方法,根據(jù)C#編程規(guī)范,異步方法應(yīng)該以Async字樣作為后綴,但如果這樣做的話,那么在CreateAsync
這個方法返回CreatedAtAction(nameof(GetByIdAsync), new { id }, sticker)
時,就會報(bào)如下的錯誤:
System.InvalidOperationException: No route matches the supplied values.
解決方案很簡單,在Program.cs
文件中,調(diào)用builder.Services.AddControllers();
方法時,將它改為:
builder.Services.AddControllers(options =>
{
options.SuppressAsyncSuffixInActionNames = false;
// 其它代碼省略...
});
至此,StickersController的基本部分已經(jīng)完成了,啟動整個項(xiàng)目,打開Swagger頁面,就可以看到我們所開發(fā)的幾個API。現(xiàn)在就可以直接在Swagger頁面中調(diào)用這些方法來體驗(yàn)我們的Sticker微服務(wù)所提供的這些RESTful API了:
本文介紹了我們案例中Sticker微服務(wù)的基本實(shí)現(xiàn),包括數(shù)據(jù)訪問部分和Sticker RESTful API的設(shè)計(jì)與實(shí)現(xiàn),雖然目前我們只是使用一個InMemoryDataAccessor
來模擬后端的數(shù)據(jù)存儲,但Sticker微服務(wù)的基本功能都已經(jīng)有了。然而,為了實(shí)現(xiàn)云原生,我們還需要向這個Sticker微服務(wù)加入一些與業(yè)務(wù)無關(guān)的東西,比如:加入日志功能以支持運(yùn)行時問題的追蹤和診斷;加入健康狀態(tài)檢測機(jī)制(health check)以支持服務(wù)狀態(tài)監(jiān)控和運(yùn)行實(shí)例調(diào)度,此外還有RESTful API Swagger文檔的完善、使用版本號和Git Hash來支持持續(xù)集成與持續(xù)部署等等,這些內(nèi)容看起來挺簡單,但也是需要花費(fèi)一定的時間和精力來遵循標(biāo)準(zhǔn)的最佳實(shí)踐。在我們真正完成了Sticker微服務(wù)后,我會使用獨(dú)立的篇幅來介紹這些內(nèi)容。
此外,ASP.NET Core Web API的功能也不僅僅局限于我們目前用到的這些,由于我們的重點(diǎn)不在ASP.NET Core Web API本身的學(xué)習(xí)上,所以這里也只會涵蓋用到的這些功能,對ASP.NET Core Web API整套體系知識結(jié)構(gòu)感興趣的讀者,建議閱讀微軟官方文檔。
下一講我將介紹如何使用PostgreSQL作為Sticker微服務(wù)的數(shù)據(jù)庫,從這一講開始,我將逐步引入容器技術(shù)。
本章源代碼請參考這里:https://gitee.com/daxnet/stickers/tree/chapter_2/
文章轉(zhuǎn)自微信公眾號@DotNet NB