.netcore 3.1高性能微服务架构:webapi规范

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介:

1.1 定义
1、基础接口:单一职责原则,每个接口只负责各自的业务,下接db,通用性强。

2、聚合接口:根据调用方需求聚合基础接口数据,业务性强。

1.2 协议

  1. 客户端在通过 API 与后端服务通信的过程中, 应该使用 HTTPS(生产环境) 协议
  2. 服务端响应的数据格式统一为JSON

1.3域名host
prd环境:https://xxx-xxx-api.example.com/

uat环境:https://xxx-xxx-api-uat.example.com/

test环境:https://xxx-xxx-api-test.example.com/

dev环境:https://xxx-xxx-api-dev.example.com/

将api放到子域名里,这种做法可以保持某些规模化上的灵活性。

1.4路径path
path命名应该是以资源为导向的命名,对资源的操作是由HttpMethod(get、post、put、delete)来决定。所以一般来说url上的单词都应该是名词,一定不要是动词。一般遵循以下约定:

(1)URL 的命名必须全部小写;
(2) URL 必须 是易读的 URL;
(3)一定不可 暴露服务器架构

(4)出现复合词汇使用下划线分隔,例如:animal_types

举几个正面例子:

新增用户:http://localhost/user post方法提交;

修改用户:http://localhost/users put方法提交;

删除文章:http://localhost/articles?author=1&category=2 delete方法提交;

查询用户:http://localhost/users get方法提交;

查询文章:http://localhost/articles?author=1&category=2get方法提交;

错误的例子如下:

http://localhost/get_user

https://api.example.com/getUserInfo?userid=1

https://api.example.com/getusers

https://api.example.com/sv/u

https://api.example.com/cgi-bin/users/get_user.php?userid=1

1.5动词
RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构,动词通常就是四种 HTTP 方法,对应 CRUD 操作:

GET(SELECT):从服务器取出资源(一项或多项)。

POST(CREATE):在服务器新建一个资源。

PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

DELETE(DELETE):从服务器删除资源。

其中

(1)删除资源 必须 用 DELETE 方法

(2)创建新的资源 必须 使用 POST 方法

(3)更新资源 应该 使用 PUT 方法

(4)获取资源信息 必须 使用 GET 方法

针对每一个路径来说,下面列出所有可行的 HTTP 动词和端点的组合

请求方

URL

描述

GET

/zoos

列出所有的动物园(ID和名称,不要太详细)

POST

/zoos

新增一个新的动物园

GET

/zoos/{zoo}

获取指定动物园详情

PUT

/zoos/{zoo}

更新指定动物园(整个对象)

PATCH

/zoos/{zoo}

更新动物园(部分对象)

DELETE

/zoos/{zoo}

删除指定动物园

GET

/zoos/{zoo}/animals

检索指定动物园下的动物列表(ID和名称,不要太详

细)

GET

/animals

列出所有动物(ID和名称)。

POST

/animals

新增新的动物

GET

/animals/{animal}

获取指定的动物详情

PUT

/animals/{animal}

更新指定的动物(整个对象)

PATCH

/animals/{animal}

更新指定的动物(部分对象)

GET

/animal_types

获取所有动物类型(ID和名称,不要太详细)

GET

/animal_types/{type}

获取指定的动物类型详情

GET

/employees

检索整个雇员列表

GET

/employees/{employee}

检索指定特定的员工

GET

/zoos/{zoo}/employees

检索在这个动物园工作的雇员的名单(身份证和姓名)

POST

/employees

新增指定新员工

POST

/zoos/{zoo}/employees

在特定的动物园雇佣一名员工

DELETE

/zoos/{zoo}/employees/{employee}

从某个动物园解雇一名员工

1.6入参

1、如果记录数量很多,服务器不可能都将它们返回给用户。API 应该 提供参数,过滤返回结果。下面是一些常见的参数。

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

所有URL参数 必须是全小写,必须使用下划线类型的参数形式。

分页参数 必须 固定为 page 、 per_page

经常使用的、复杂的查询 应该 标签化,降低维护成本,如

GET /trades?status=closed&sort=sortby=name&order=asc

可为其定制快捷方式

GET /trades/recently_closed

2、入参可分为业务参数和公共参数;公共参数有:

参数

名称

说明

timestamp

时间戳

clientid

调用方appid

统一管理应用,否则不放行

token

令牌

幂等情况可用

version

版本号

1.7响应
1、出参(返回值):必须的字段有:

字段

类型

描述

code

数值

状态码

msg

字符串

信息描述

data

结果集

返回结果集

2、如果请求处理完全正确,则状态码为0 ;

3、状态码暂定8位数数字,前4位为某一个应用(服务)拟的一个数字,后4位为具体的状态值。状态码分为2种---公共和自定义,公共码以0打头+3位数。
比如:

99990400 --客户端错误,比如请求语法格式错误、无效的请求、无效的签名等。

99991001 -----用户Id不能为空

响应的公共码如下:

编码

描述

说明

001

注解使用错误

 

002

微服务不在线,或网络超时

 

003

TOKEN解析失败

 

004

TOKEN无效或没有对应的用户

 

400

客户端错误,比如请求语法格式错误、
无效的请求、无效的签名等。

服务器 应该 放弃该请求

401

需要身份认证,比如access_token 无效/过期

客户端在收到 401 响应后,
都 应该 提示用户进行下一步的登录操作

403

没有权限访问该请求

服务器收到请求但拒绝提供服务。
如当普通用户请求操作管理员用户时,
必须 返回该状态码

404

用户请求的资源不存在

如获取不存在的用户信息

410

请求的资源不存在,并且未来也不会存在

在收到 410 状态码后,
客户端 应该 停止再次请求该资源。

429

请求次数超过允许范围

 

500

未知异常

应该 提供完整的错误信息支持,也方便跟踪调试

1.8项目结构

1、采用经典DDD领域取到模型:(默认一个解决方案有5个项目)

5个项目分别为:

Web层为最外层接口定义;

Service为具体的应用服务处理;

Infrastructure基础设施层,处理具体的业务逻辑和数据DB的处理;

Domain领域层为模型和仓库接口interface;

Common为通用的一些Helper类;

2、一个解决方案创建5个项目(如上图),并且里包含常用的基础组件:Log4net日志,听云监听;dockerfile,skywalking,全局异常捕捉,接口请求开始和结束的日志记录,swagger,service层的依赖注入,Mapping等。

3、代码全部采用依赖注入写法,尽量少些静态类;

4、HttpClient的写法:使用采用.netcore官方提供的方法,采用工厂类+依赖注入方式:实例代码如下:

复制代码
1、SartUp类里添加代码-- httpclient初始化:
services.AddHttpClient("MsgApi", c =>

        {
            c.BaseAddress = new Uri(Configuration["OuterApi:MsgApi:url"]);
            c.Timeout = TimeSpan.FromSeconds(30);
        });

//2 构造函注入
private IDbContext _dbContext;
private IUnitOfWork _unitOfWork;
private IordersRepository _ordersRepository;
private IordercourseRepository _ordercourseRepository;
private ILogger _logger;
privatereadonly IConfiguration _config;
privatereadonly IHttpClientFactory _clientFactory;

public ordersService(IDbContext dbContext, ILogger logger, IConfiguration config, IHttpClientFactory clientFactory)

    {
        _dbContext = dbContext;
        _unitOfWork = new UnitOfWork(_dbContext);
        _ordersRepository = new ordersRepository(_dbContext);
        _ordercourseRepository = new ordercourseRepository(_dbContext);
        _mapper = mapper;
        _config = config;
        _logger = logger;
        _clientFactory = clientFactory;
    }

//3使用
///
///判断此时该校区是否可以下单
///
///
///
publicasync Task> CheckDept(CheckSchoolDeptReq req)

    {
        Result<string> sendRet = new Result<string>();

try

        {
            HttpClient client = _clientFactory.CreateClient("ContractApi");
            MyHttpClientHelper myHttpClientHelper = new MyHttpClientHelper();
            MarketToUPCCheckReq checkreq = new MarketToUPCCheckReq();
            sendRet = await myHttpClientHelper.GetData<Result<string>>(client, "MarketToUPCCheck", checkreq);
        }

catch (Exception ex)

        {
            sendRet.state = false;
            sendRet.error_code = ErrorCode.SysExceptionError;
            sendRet.error_msg = "调用《是否可以下订单接口》报错了。请重试或者联系管理员!";
            _logger.LogError(ex, ErrorCode.SysExceptionError +"调用《是否可以下订单》接口报错了:" + ex.Message);
        }

return sendRet;

    }

复制代码

1.9日志

1、接口开始前和结束后都已在LogstashFilter里记录,接口里就不需要再次记录;

LogstashFilter里的代码如下:

复制代码
///

/// 记录日志用过滤器
/// </summary>
public class LogstashFilter : IActionFilter, IResultFilter
{
    private string ActionArguments { get; set; }

    /// <summary>
    /// 请求体中的所有值
    /// </summary>
    private string RequestBody { get; set; }
    private Stopwatch Stopwatch { get; set; }

    private ILogger _logger;
  
    public LogstashFilter(ILogger<LogstashFilter> logger )
    {
        _logger = logger;
        
    }

    /// <summary>
    /// Action 调用前执行
    /// </summary>
    /// <param name="context"></param>
    public void OnActionExecuting(ActionExecutingContext context)
    {
          
        long contentLen = context.HttpContext.Request.ContentLength == null ? 0 : context.HttpContext.Request.ContentLength.Value;
        if (contentLen > 0)
        {
            // 读取请求体中所有内容
            System.IO.Stream stream = context.HttpContext.Request.Body;
            if (context.HttpContext.Request.Method == "POST")
            {
                stream.Position = 0;
            }
            byte[] buffer = new byte[contentLen];
            stream.Read(buffer, 0, buffer.Length);

            RequestBody = System.Text.Encoding.UTF8.GetString(buffer);// 转化为字符串
        }

        ActionArguments = JsonConvert.SerializeObject(context.ActionArguments);

        Stopwatch = new Stopwatch();
        Stopwatch.Start();
        string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
        string method = context.HttpContext.Request.Method;
       
        _logger.LogInformation($"地址:{url} \n " +
           $"方式:{method} \n " +
           $"请求体:{RequestBody} \n " +
           $"完整参数:{ActionArguments}\n " );
    }

    /// <summary>
    /// Action 方法调用后,Result 方法调用前执行
    /// </summary>
    /// <param name="context"></param>
    public void OnActionExecuted(ActionExecutedContext context)
    {
        // do nothing
    }

    /// <summary>
    /// Result 方法调用前(View 呈现前)执行
    /// </summary>
    /// <param name="context"></param>
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // do nothing
    }

    /// <summary>
    /// Result 方法调用后执行
    /// </summary>
    /// <param name="context"></param>
    public void OnResultExecuted(ResultExecutedContext context)
    {

        Stopwatch.Stop();
        string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
            string method = context.HttpContext.Request.Method;
            string qs = ActionArguments;
            string res = "在返回结果前发生了异常";
            if (context.Result is ObjectResult)
            {
                dynamic result = context.Result.GetType().Name == "EmptyResult" ? new { Value = "无返回结果" } : context.Result as dynamic;
                if (result != null)
                {
                    res = JsonConvert.SerializeObject(result.Value);
                }

            }

            _logger.LogInformation($"地址:{url} \n " +
                $"方式:{method} \n " +
                $"请求体:{RequestBody} \n " +
                $"参数:{qs}\n " +
                $"结果:{res}\n " +
                $"耗时:{Stopwatch.Elapsed.TotalMilliseconds} 毫秒");

        
    }
}

复制代码

2、try Catch日志必须要添加LogError日志,并且要将堆栈信息记录,代码如下:

catch (Exception ex)

        { 
            _logger.LogError(ex, ErrorCode500 + ex.Message);
        }
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
10天前
|
消息中间件 缓存 Java
高性能架构设计
高性能架构设计
22 5
|
17天前
|
缓存 负载均衡 监控
构建高性能的后端服务架构
在当今快速发展的技术环境下,构建高性能的后端服务架构变得至关重要。本文将探讨如何通过优化数据库设计、采用合适的缓存策略、以及合理使用负载均衡等方法来构建高性能的后端服务架构,以满足用户对于性能和稳定性的需求。
18 0
|
18天前
|
缓存 API 数据库
构建高性能微服务架构:后端开发的新趋势
【5月更文挑战第28天】 在现代软件开发的浪潮中,微服务架构已成为推动技术创新与提升系统可维护性的关键。此文深入剖析了构建高性能微服务架构的策略和实践,揭示了通过容器化、服务网格、持续集成/持续部署(CI/CD)等技术手段来优化后端开发流程的方法。文章不仅探讨了微服务设计原则和性能优化技巧,还提供了对未来后端开发趋势的预见性分析。
|
18天前
|
持续交付 Android开发 开发者
构建高性能微服务架构:后端开发的终极指南构建高效Android应用:Kotlin与Jetpack的完美结合
【5月更文挑战第28天】 在现代软件开发的浪潮中,微服务架构已经成为了设计灵活、可扩展且易于维护系统的重要模式。本文将深入探讨如何构建一个高性能的微服务架构,涵盖从基础概念理解到实践策略部署的全过程。我们将讨论关键的设计原则、技术选型、性能优化技巧以及安全性考虑,旨在为后端开发者提供一个全面的指南,帮助他们构建出能够适应快速变化的市场需求和技术挑战的系统。 【5月更文挑战第28天】 在移动开发的世界中,效率和性能是衡量一个应用成功与否的关键因素。本文将深入探讨如何通过结合Kotlin语言和Android Jetpack组件,来构建一个既高效又易维护的Android应用。我们将透过实际案例分析
|
19天前
|
消息中间件 缓存 数据库
构建高性能微服务架构:从理论到实践
【5月更文挑战第27天】在现代软件开发中,微服务架构已成为实现可扩展、灵活和容错系统的关键设计模式。本文将深入探讨如何构建一个高性能的微服务系统,包括关键的设计理念、技术选型以及性能优化策略。我们将通过分析真实案例,提供一套实用的指导原则和最佳实践,帮助开发者提升系统的响应速度和处理能力,从而满足不断变化的业务需求。
|
19天前
|
消息中间件 运维 监控
构建高性能微服务架构:策略与实践
【5月更文挑战第27天】在现代软件开发领域,微服务架构已经成为设计灵活、可扩展系统的首选模式。本文将深入探讨构建高性能微服务的策略和实践,包括服务拆分原则、通信机制优化、容器化部署、以及持续监控与调优等方面。通过分析真实案例,我们将提供一套系统的方法论来指导开发人员和架构师在保证系统稳定性的前提下提升服务的响应速度和处理能力。
|
19天前
|
消息中间件 API 数据库
构建高性能微服务架构:后端开发的终极指南
【5月更文挑战第27天】随着现代业务需求的不断演进,传统的单体应用架构逐渐显得笨重且难以维护。微服务架构以其灵活性、可扩展性和技术多样性成为企业转型的首选。本文将深入探讨如何构建一个高性能的微服务系统,涵盖关键设计原则、常用技术和实践案例,为后端开发人员提供一份全面的指南。
|
20天前
|
缓存 监控 数据库
构建高性能微服务架构:后端开发的进阶之路
【5月更文挑战第26天】 在当今的软件开发世界中,微服务架构已成为一种流行的设计模式,它允许复杂应用程序分解为一组独立的服务,每个服务负责应用程序的一个特定部分。这种模块化方法提高了系统的可维护性和扩展性,同时促进了敏捷开发和部署。本文将探讨如何构建一个高性能的微服务架构,包括关键的设计原则、技术栈选择、以及性能优化策略。我们的目标是为后端开发人员提供一条清晰的道路,以便他们能够构建出既健壮又高效的微服务系统。
|
2天前
|
监控 Cloud Native 开发者
云原生技术浪潮下的微服务架构实践
云原生技术正引领着现代软件开发的潮流,其中微服务架构作为其核心理念之一,为复杂应用提供了灵活、可扩展的解决方案。本文将探讨在云原生环境下实施微服务架构的策略和挑战,并结合实际案例分析微服务设计的最佳实践,旨在为开发者提供一套可行的微服务部署与管理指南。
|
2天前
|
消息中间件 监控 API
构建微服务架构:从理论到实践的全面指南
本文将深入探讨微服务架构的设计原则、实施步骤和面临的挑战。与传统的单体架构相比,微服务通过其独立性、可伸缩性和灵活性,为现代应用开发提供了新的视角。文章将介绍如何从零开始规划和部署一个微服务系统,包括选择合适的技术栈、处理数据一致性问题以及实现服务间通信。此外,我们还将讨论在迁移至微服务架构过程中可能遇到的技术和组织挑战,以及如何克服这些难题以实现顺利过渡。