惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Troy Hunt's Blog
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
T
Tenable Blog
L
LINUX DO - 热门话题
V
Visual Studio Blog
I
Intezer
Blog — PlanetScale
Blog — PlanetScale
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
Cyber Attacks, Cyber Crime and Cyber Security
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
Netflix TechBlog - Medium
SecWiki News
SecWiki News
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Project Zero
Project Zero
W
WeLiveSecurity
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Vercel News
Vercel News
S
Securelist
Spread Privacy
Spread Privacy
L
LangChain Blog
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
罗磊的独立博客
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
博客园 - 司徒正美
Help Net Security
Help Net Security
L
Lohrmann on Cybersecurity
人人都是产品经理
人人都是产品经理
Forbes - Security
Forbes - Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
博客园 - 【当耐特】
T
Tor Project blog

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
gRPC 借助 Any 类型实现接口的泛化调用 - 元视角
飞鸿踏雪 · 2021-12-10 · via 元视角

我发现,人们非常喜欢在一件事情上反复横跳。譬如,以编程语言为例,人们喜欢静态的、强类型语言的严谨和安全,可难免会羡慕动态的、弱类型语言的自由和灵活。于是,在过去的这些年里,我们注意到,.NET 的世界里出现了 dynamic 类型,JavaScript 的世界里出现了 TypeScript,甚至连 Python 都开始支持类型标注。这种动与静、强与弱的角逐,隐隐然有种太极圆转、轮回不绝的感觉。果然,“城外的人想冲进去,城里的人想逃出来”,钱钟书先生说的固然是婚姻,可世上的事情,也许都差不多罢!人们反复横跳的样子,像极了「九品芝麻官」里的方唐镜。曾经有段时间,好多人吹捧 Vue3 + TypeScript 的技术栈,有位前辈一针见血地戳破了这种叶公好龙式的喜欢,“你那么喜欢 TypeScript,不还是关掉了 ESLint 的规则,项目里全部都用 Any”。对于这个吐槽,我表示非常真实,因为我们对于动与静、强与弱的心理变化是非常微妙的。常言道,“动态类型一时爽,代码重构火葬场”,你是如何看待编程语言里的动与静静、强与弱的呢?在 gRPC 中我们通过 Protobuf 来描述接口的参数和返回值,由此对服务提供/消费方进行约束。此时,参数和返回值都是静态的、强类型的。如果我们希望提供某种“泛型”的接口,又该如何去做呢?所以,这篇文章我们来聊聊 gPRC 里的 Any 类型。

Protobuf 里的 Any 类型

在讲 Any 类型前,我想,我们应该想明白,为什么需要这样一个类型?现在,假设我们有下面的 Protobuf 定义:

// Vehicle
message Vehicle {
  int32 VehicleId = 1;
  string FleetNo = 2;
}

// Officer
message Officer {
  int32 OfficerId = 1;
  string Department = 2;
} 

此时,按照Protobuf的规范,我们必须像下面这样定义对应的集合:

// VehicleList
message VehicleList {
  repeated Vehicle List = 1;
}

// OfficerList
message OfficerList {
  repeated Officer List = 1;
} 

考虑到,在C# 中我们只需要使用 List<Vehicle>List<Officer> 即可,这样难免就会形成一种割裂感,因为你几乎要为每一种类型建立对应的表示集合的类型,从语义化的角度考虑,我们更希望使用下面的 Protobuf 定义:

message Collection {
  repeated Any List = 1;
}

此时,VehicleListOfficerList 就可以统一到 Collection 这个类型中,这样,不但减少了花在类型定义的时间,更能帮助我们打开一点思路。在过去,我们编写 API 的时候,通常会定义下面的类来返回结果:

public class ApiResult<TData> {
  public int Code { get; set; }
  public string Msg { get; set; }
  public TData Data { get; set; }
}

类似地,当我们用 gPRC 来做微服务的时候,我们希望在 Protobuf 中沿用这个设计:

message ApiResult {
  int32 Code = 1;
  string Msg = 2;
  Any Data = 3;
}

至此,它可以和我们在 C# 中的认知联系起来,不会让你有太多心智上的负担。基于上述两种诉求,我们发现, Protobuf 中存在着需要泛化的场景,你可以理解为,我们需要用 Protobuf 来表示泛型或者模板类这样的东西。幸运的是,Google 为我们定义了 Any 类型,它到底是何方神圣呢?我们一起来看看:

message Any {
  string type_url = 1;
  bytes value = 2;
}

没错,它就是这样的朴实无华,甚至比古天乐还要平平无奇,简单来说,type_url字段告诉你这是一个什么类型,value字段里则存放对应的二进制数据,而这就是 Any 类型的全部秘密!

在 .NET 中使用 Any 类型

好了,下面我们来演示,如何在 .NET 中使用 Any 类型。通过前面我们已经知道, Any 类型和我们自定义的消息没有区别,所以,它同样实现了 IMessageIMessage<Any>两个接口,唯一不同的地方在于,它拥有Pack()Unpack<T>()TryUnpack<T>()这样几个静态方法,这是实现任意 IMessageAny 相互转换的关键。现在,假设我们现在有如下的 Protobuf 定义:

message AnyRequest {
  google.protobuf.Any Data = 1; 
}

message AnyResponse {
  google.protobuf.Any Data = 1;
}

message Foo {
  string Name = 1;
}

message Bar {
  string Name = 1;
}

此时,如果我们希望在 AnyRequest 或者 AnyResponse 里传递 Any 类型,我们可以这样做:

var anyRequest = new AnyRequest()

// Foo -> Any,默认类型地址前缀
var foo = new Foo();
foo.Name = "Foo";
anyRequest.Data = Any.Pack(foo);

// Bar -> Any, 自定义类型地址前缀
var bar = new Bar();
bar.Name = "Bar";
anyRequest.Data = Any.Pack(bar, "type.company.com/bar");

反过来,我们可以从 Any 中解析出 IMessage

if (request.Data.Is(Foo.Descriptor))
{
  // Any -> Foo
  var foo = request.Data.Unapck<Foo>();
} 
else if (request.Data.Is(Bar.Descriptor))
{
  // Any -> Bar
  var bar = request.Data.Unapck<Bar>();
}

默认的 Any 类型,只能对 Protobuf 生成的类型(即实现了 IMessage 接口)进行 Pack ,如果我们想做得更绝一点(最好还是不要),那么,可以使用自定义的 MyAny 类型:

message MyAny {
  string TypeUrl = 1;
  bytes Value = 2;
}

相应地,我们为 MyAny 类型编写一点扩展方法:

public static class MyAnyExtension
{
    public static MyAny Pack(this object obj, string typeUrlPrefix = "")
    {
        var any = new MyAny();
        any.TypeUrl = $"{typeUrlPrefix}/{obj.GetType().FullName}";
        var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
        any.Value = Google.Protobuf.ByteString.CopyFrom(bytes);
        return any;
    }

    public static T Unpack<T>(this MyAny any, string typeUrlPrefix = "")
    {
        var typeUrl = $"{typeUrlPrefix}/{typeof(T).FullName}";
        if (typeUrl == any.TypeUrl)
        {
            var json = Encoding.UTF8.GetString(any.Value.ToByteArray());
            return JsonConvert.DeserializeObject<T>(json);
        }

        return default(T);
    }

    public static bool Is<T>(this MyAny any, string typeUrlPrefix = "")
    {
        var typeUrl = $"{typeUrlPrefix}/{typeof(T).FullName}";
        return typeUrl == any.TypeUrl;
    }
}

接下来,我们就可以对任意类型进行处理,虽然,此时此刻,从严格意义上来讲,它已不再属于 Protobuf 的范畴,因为序列化/反序列化都交给了 JSON :

var client = serviceProvider.GetService<ProtobufAny.Greeter.GreeterClient>();
client.Ping(new Foo() { Name = "Foo" }.Pack());
client.Ping(new Bar() { Name = "Foo" }.Pack());
client.Ping(new { X = 0, Y = 1, Z = 0 }.Pack());

这样看起来是不是非常酷?我始终认为,这件事情是有意义的,一个系统中最多的接口显然是查询接口,此时,我们可以构建一个通用的 查询 来处理,使用者只需要传递一个实体、一个Proto,一组过滤条件,它就可以返回对应的数据,这样是不是比写一个又一个差不多的接口要好一点呢?过去我们开发 API,主张用数据传输对象(DTO)来隔离持久化层和业务层,从这个角度来看,Protobuf 本身就是 一种 DTO ,对于大多数相似的、模板化的、套路化的接口,我们完全可以考虑用这种方案来实现,只要双方约定好类型即可。

// 在业务层构建通用的查询
public QueryReply Query<TInput, TOutput>(SearchParameters searchParameters) where TInput : class
{
    var result = _chinookContext.Set<TInput>().AsQueryable().Search(searchParameters).ToList();
    var output = result.Adapt<List<TOutput>>();

    var reply = new QueryReply();
    reply.List.AddRange(output.Select(x => x.Pack()));
    return reply;
}

// x => { 1, 2, 3 }.Contains(x.AlbumId)
var searchParameters = SearchParameters();
searchParameters.QueryModel = new QueryModel();
searchParameters.QueryModel.Add(new Condition() { Field = "AlbumId", Op = Operation.StdIn, Value = new int[] { 1, 2, 3} });

// 在服务层解析参数,完全可以由调用方提供 SearchParameters
var inputType = Type.GetType(request.InputType);
var outputType = Type.GetType(request.OutputType);
if (inputType != null && outputType != null)
{
    var queryMethod = _queryService.GetType().GetMethod("Query").MakeGenericMethod(inputType, outputType);
    QueryReply queryResult = (QueryReply)queryMethod.Invoke(_queryService, new object[] { 
      new DynamicSearch.Core.SearchParameters()
    });
    return Task.FromResult(queryResult);
}

本文小结

对于编程语言中的动与静、强与弱,我个人觉得还是要看场景,只要双方定义好契约,我相信,它都可以运作起来,当然,更多的时候,我们是在灵活与严谨间反复横跳。作为一门 DSL,Protobuf 虽然可以对服务提供/消费方产生一定约束,可当我们面对需要泛型或者模板类的场景的时候,这种做法就变成了一种负担,更不必说它缺乏对继承的支持。想象一下,你要写二十多个大同小异的接口,譬如为每一张数据表写一个 GetXXXById() 的接口。此时,我们可以借助 Any 类型来实现类似泛型、模板类的东西,它本质上还是 IMessage 接口的实现类,唯一的不同是增加了 Pack/Unpack 这组静态方法,可以帮助我们实现 AnyIMessage 的相互转换,关于本文中使用的的实例,可以参考:ProtobufAny,好了,以上就是这篇博客的全部内容,如果有朋友对文章中的内容和观点存在疑问,欢迎在评论区积极留言,谢谢大家!