docs: add Bazel build guide and bzlmod example (#3468)

* docs: add Bazel build guide for bRPC (#13)

* docs: fix build_with_bazel_module echo example

* docs: explain why the bzlmod example repeats the root overrides (#24)

---------

Co-authored-by: Winchell <cw20050111@gmail.com>
diff --git a/docs/cn/bazel_support.md b/docs/cn/bazel_support.md
index 0ff8611..3a138cf 100644
--- a/docs/cn/bazel_support.md
+++ b/docs/cn/bazel_support.md
@@ -1,20 +1,100 @@
-## bRPC 作为Bazel第三方依赖
-1. bRPC 依赖于一些开源库, 但这些库并没有提供bazel支持, 所以需要你手动将一部分依赖加入到你的构建项目中.
-2. 将 /example/build_with_bazel/*.BUILD 和 brpc_workspace.bzl 该文件移动到你的项目根目录下, 将
-```c++
-    load("@//:brpc_workspace.bzl", "brpc_workspace")
-    brpc_workspace();
-```
-内容添加到你的WORKSPACE中.
+## bRPC 作为 Bazel 第三方依赖
 
-3. 链接请使用
-  ```c++
-  ...
-  deps = [
+推荐在 Bazel 项目中使用 bzlmod(`MODULE.bazel`)依赖本地 bRPC 源码。
+`example/build_with_bazel_module` 中有一个包含 server 和 client 的可运行示例:
+
+```shell
+$ cd example/build_with_bazel_module
+$ bazel build //:echo_c++_server //:echo_c++_client
+$ bazel run //:echo_c++_server &
+$ bazel run //:echo_c++_client
+```
+
+先在你的 `.bazelrc` 中添加 bRPC 使用的 registry:
+
+```shell
+common --registry=https://bcr.bazel.build
+common --registry=https://baidu.github.io/babylon/registry
+common --registry=https://raw.githubusercontent.com/apache/brpc/master/registry
+```
+
+然后在 `MODULE.bazel` 中添加 bRPC,并指向本地 bRPC 源码:
+
+```python
+module(
+    name = "my_brpc_app",
+    version = "0.1.0",
+)
+
+bazel_dep(name = "protobuf", version = "27.3", repo_name = "com_google_protobuf")
+bazel_dep(name = "brpc", version = "1.17.0", repo_name = "apache_brpc")
+
+local_path_override(
+    module_name = "brpc",
+    path = "/path/to/brpc",
+)
+
+single_version_override(
+    module_name = "leveldb",
+    registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main",
+)
+
+single_version_override(
+    module_name = "openssl",
+    version = "3.3.2.bcr.1",
+    registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main",
+)
+```
+
+`bazel_dep` 用来声明模块名和仓库映射,`local_path_override` 让 Bazel 使用本地源码,而不是从 registry 解析 bRPC。
+
+bzlmod 不会传递依赖模块中的 override。把 bRPC 当依赖时,需要在自己项目的根
+`MODULE.bazel` 里重复上面的 `single_version_override`;它们在 aarch64 上是必需的,否则 leveldb toolchain 解析会失败。
+
+之后在目标中链接 bRPC:
+
+```python
+cc_binary(
+    name = "server",
+    srcs = ["server.cpp"],
+    deps = [
+        "@apache_brpc//:brpc",
+    ],
+)
+```
+
+如果服务使用 protobuf,可以从 bRPC 加载 `brpc_proto_library`:
+
+```python
+load("@apache_brpc//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library")
+
+brpc_proto_library(
+    name = "cc_echo_proto",
+    srcs = ["echo.proto"],
+)
+```
+
+## 旧版 WORKSPACE 用法
+
+仍在使用 `WORKSPACE` 的项目可以参考 `example/build_with_bazel`。
+
+1. 将 `example/build_with_bazel/*.BUILD` 和
+   `example/build_with_bazel/brpc_workspace.bzl` 移动到你的项目根目录下。
+2. 在 `WORKSPACE` 中添加:
+
+```python
+load("@//:brpc_workspace.bzl", "brpc_workspace")
+
+brpc_workspace()
+```
+
+3. 在目标中链接 `apache_brpc`:
+
+```python
+deps = [
     "@apache_brpc//:bthread",
     "@apache_brpc//:brpc",
     "@apache_brpc//:butil",
     "@apache_brpc//:bvar",
-  ]
-  ...
-  ```
+]
+```
diff --git a/docs/cn/getting_started.md b/docs/cn/getting_started.md
index 334906a..51e9b97 100644
--- a/docs/cn/getting_started.md
+++ b/docs/cn/getting_started.md
@@ -121,6 +121,19 @@
 $ mkdir build && cd build && cmake -DBUILD_UNIT_TESTS=ON .. && make && make test
 ```
 
+### 使用 Bazel 编译 brpc
+
+bRPC 也支持 Bazel 构建。`example/build_with_bazel_module` 下的 bzlmod 示例会基于本地
+bRPC 源码构建 server 和 client:
+
+```shell
+$ cd example/build_with_bazel_module
+$ bazel build //:echo_c++_server //:echo_c++_client
+```
+
+如果要把 bRPC 作为 Bazel 依赖使用,包括需要配置的 registry 和 `MODULE.bazel`,
+请参考 [Bazel 支持](bazel_support.md)。
+
 ## Fedora/CentOS
 
 ### 依赖准备
diff --git a/docs/en/bazel_support.md b/docs/en/bazel_support.md
index 607cf9c..d149760 100644
--- a/docs/en/bazel_support.md
+++ b/docs/en/bazel_support.md
@@ -1,20 +1,105 @@
 ## bRPC as a Bazel third-party dependency
-1. bRPC relies on a number of open source libraries that do not provide bazel support, so you will need to manually add some of these dependencies to your build project.
-2. Move the BUILD file /example/build_with_bazel/*.BUILD and brpc_workspace.bzl to the root of your project, and add the contents of 
-```c++
-    load("@//:brpc_workspace.bzl", "brpc_workspace")
-    brpc_workspace();
-```
-to your WORKSPACE
 
-3. link apache_brpc like:
-  ```c++
-  ...
-  deps = [
+The recommended way to depend on a local bRPC checkout from a Bazel project is
+to use bzlmod (`MODULE.bazel`). See `example/build_with_bazel_module` for a
+runnable example with both a server and a client:
+
+```shell
+$ cd example/build_with_bazel_module
+$ bazel build //:echo_c++_server //:echo_c++_client
+$ bazel run //:echo_c++_server &
+$ bazel run //:echo_c++_client
+```
+
+Add the registries used by bRPC to your `.bazelrc`:
+
+```shell
+common --registry=https://bcr.bazel.build
+common --registry=https://baidu.github.io/babylon/registry
+common --registry=https://raw.githubusercontent.com/apache/brpc/master/registry
+```
+
+Add bRPC to your `MODULE.bazel`, and point it to your local bRPC checkout:
+
+```python
+module(
+    name = "my_brpc_app",
+    version = "0.1.0",
+)
+
+bazel_dep(name = "protobuf", version = "27.3", repo_name = "com_google_protobuf")
+bazel_dep(name = "brpc", version = "1.17.0", repo_name = "apache_brpc")
+
+local_path_override(
+    module_name = "brpc",
+    path = "/path/to/brpc",
+)
+
+single_version_override(
+    module_name = "leveldb",
+    registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main",
+)
+
+single_version_override(
+    module_name = "openssl",
+    version = "3.3.2.bcr.1",
+    registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main",
+)
+```
+
+The `bazel_dep` keeps the module name and repository mapping, while
+`local_path_override` makes Bazel use the local checkout instead of resolving
+bRPC from a registry.
+
+bzlmod does not propagate overrides from dependency modules. When using bRPC as
+a dependency, repeat the `single_version_override` entries above in your root
+`MODULE.bazel`; they are required on aarch64 because leveldb toolchain
+resolution otherwise fails.
+
+Then link bRPC from your targets:
+
+```python
+cc_binary(
+    name = "server",
+    srcs = ["server.cpp"],
+    deps = [
+        "@apache_brpc//:brpc",
+    ],
+)
+```
+
+If your service uses protobuf, load `brpc_proto_library` from bRPC:
+
+```python
+load("@apache_brpc//bazel/tools:brpc_proto_library.bzl", "brpc_proto_library")
+
+brpc_proto_library(
+    name = "cc_echo_proto",
+    srcs = ["echo.proto"],
+)
+```
+
+## Legacy WORKSPACE usage
+
+For projects that still use `WORKSPACE`, see `example/build_with_bazel`.
+
+1. Move `example/build_with_bazel/*.BUILD` and
+   `example/build_with_bazel/brpc_workspace.bzl` to the root of your project.
+2. Add the following to your `WORKSPACE`:
+
+```python
+load("@//:brpc_workspace.bzl", "brpc_workspace")
+
+brpc_workspace()
+```
+
+3. Link `apache_brpc` from your targets:
+
+```python
+deps = [
     "@apache_brpc//:bthread",
     "@apache_brpc//:brpc",
     "@apache_brpc//:butil",
     "@apache_brpc//:bvar",
-  ]
-  ...
-  ```
+]
+```
diff --git a/docs/en/getting_started.md b/docs/en/getting_started.md
index 32d4733..9acadc7 100644
--- a/docs/en/getting_started.md
+++ b/docs/en/getting_started.md
@@ -108,6 +108,20 @@
 $ mkdir build && cd build && cmake -DBUILD_UNIT_TESTS=ON .. && make && make test
 ```
 
+### Compile brpc with Bazel
+
+bRPC also supports Bazel builds. The bzlmod example under
+`example/build_with_bazel_module` builds a server and a client against the local
+bRPC checkout:
+
+```shell
+$ cd example/build_with_bazel_module
+$ bazel build //:echo_c++_server //:echo_c++_client
+```
+
+For using bRPC as a Bazel dependency, including the required registries and
+`MODULE.bazel` setup, see [Bazel support](bazel_support.md).
+
 ### Compile brpc with vcpkg
 
 [vcpkg](https://github.com/microsoft/vcpkg) is a package manager that supports all platforms,
diff --git a/example/build_with_bazel_module/BUILD.bazel b/example/build_with_bazel_module/BUILD.bazel
index 8dbc380..340a1ba 100644
--- a/example/build_with_bazel_module/BUILD.bazel
+++ b/example/build_with_bazel_module/BUILD.bazel
@@ -30,3 +30,14 @@
         "@apache_brpc//:brpc",
     ],
 )
+
+cc_binary(
+    name = "echo_c++_client",
+    srcs = [
+        "client.cpp",
+    ],
+    deps = [
+        ":cc_echo_c++_proto",
+        "@apache_brpc//:brpc",
+    ],
+)
diff --git a/example/build_with_bazel_module/MODULE.bazel b/example/build_with_bazel_module/MODULE.bazel
index e5d66a8..04a7321 100644
--- a/example/build_with_bazel_module/MODULE.bazel
+++ b/example/build_with_bazel_module/MODULE.bazel
@@ -27,4 +27,25 @@
 local_path_override(
     module_name = "brpc",
     path = "../..",
-)
\ No newline at end of file
+)
+
+# Overrides only take effect in the root module. When this example is built,
+# `brpc-example` is the root module and `brpc` is just a dependency, so the
+# same two overrides in brpc's root MODULE.bazel are ignored and have to be
+# repeated here. Keep them in sync with the root MODULE.bazel.
+#
+# leveldb is published only in the secretflow registry — not in BCR, nor in
+# the babylon / apache-brpc registries added by .bazelrc. And brpc declares
+# `openssl 3.3.2`, which exists there only as `3.3.2.bcr.1`, hence the
+# version rewrite.
+
+single_version_override(
+    module_name = "leveldb",
+    registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main",
+)
+
+single_version_override(
+    module_name = "openssl",
+    version = "3.3.2.bcr.1",
+    registry = "https://raw.githubusercontent.com/secretflow/bazel-registry/main",
+)
diff --git a/example/build_with_bazel_module/client.cpp b/example/build_with_bazel_module/client.cpp
new file mode 100644
index 0000000..0e04dac
--- /dev/null
+++ b/example/build_with_bazel_module/client.cpp
@@ -0,0 +1,89 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// A client sending requests to server every 1 second.
+
+#include <gflags/gflags.h>
+#include <butil/logging.h>
+#include <brpc/channel.h>
+#include "echo.pb.h"
+
+DEFINE_string(attachment, "", "Carry this along with requests");
+DEFINE_string(protocol, "baidu_std", "Protocol type. Defined in src/brpc/options.proto");
+DEFINE_string(connection_type, "", "Connection type. Available values: single, pooled, short");
+DEFINE_string(server, "0.0.0.0:8002", "IP Address of server");
+DEFINE_int32(timeout_ms, 100, "RPC timeout in milliseconds");
+DEFINE_int32(max_retry, 3, "Max retries(not including the first RPC)");
+DEFINE_int32(interval_ms, 1000, "Milliseconds between consecutive requests");
+
+int main(int argc, char* argv[]) {
+    // Parse gflags. We recommend you to use gflags as well.
+    GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
+
+    // A Channel represents a communication line to a Server. Notice that
+    // Channel is thread-safe and can be shared by all threads in your program.
+    brpc::Channel channel;
+
+        // Initialize the channel, nullptr means using default options.
+    brpc::ChannelOptions options;
+    options.protocol = FLAGS_protocol;
+    options.connection_type = FLAGS_connection_type;
+    options.timeout_ms = FLAGS_timeout_ms/*milliseconds*/;
+    options.max_retry = FLAGS_max_retry;
+    if (channel.Init(FLAGS_server.c_str(), &options) != 0) {
+        LOG(ERROR) << "Fail to initialize channel";
+        return -1;
+    }
+
+    // Normally, you should not call a Channel directly, but instead construct
+    // a stub Service wrapping it. stub can be shared by all threads as well.
+    example::EchoService_Stub stub(&channel);
+
+    // Send a request and wait for the response every 1 second.
+    int log_id = 0;
+    while (!brpc::IsAskedToQuit()) {
+        // We will receive response synchronously, safe to put variables
+        // on stack.
+        example::EchoRequest request;
+        example::EchoResponse response;
+        brpc::Controller cntl;
+
+        request.set_message("hello world");
+
+        cntl.set_log_id(log_id ++);  // set by user
+        // Set attachment which is wired to network directly instead of
+        // being serialized into protobuf messages.
+        cntl.request_attachment().append(FLAGS_attachment);
+
+        // Because `done'(last parameter) is nullptr, this function waits until
+        // the response comes back or error occurs(including timedout).
+        stub.Echo(&cntl, &request, &response, nullptr);
+        if (!cntl.Failed()) {
+            LOG(INFO) << "Received response from " << cntl.remote_side()
+                << " to " << cntl.local_side()
+                << ": " << response.message() << " (attached="
+                << cntl.response_attachment() << ")"
+                << " latency=" << cntl.latency_us() << "us";
+        } else {
+            LOG(WARNING) << cntl.ErrorText();
+        }
+        usleep(FLAGS_interval_ms * 1000L);
+    }
+
+    LOG(INFO) << "EchoClient is going to quit";
+    return 0;
+}
diff --git a/example/build_with_bazel_module/server.cpp b/example/build_with_bazel_module/server.cpp
index 54ca096..289b1af 100644
--- a/example/build_with_bazel_module/server.cpp
+++ b/example/build_with_bazel_module/server.cpp
@@ -83,8 +83,6 @@
 
     // Start the server. 
     brpc::ServerOptions options;
-    options.mutable_ssl_options()->default_cert.certificate = "cert.pem";
-    options.mutable_ssl_options()->default_cert.private_key = "key.pem";
     options.idle_timeout_sec = FLAGS_idle_timeout_s;
     options.max_concurrency = FLAGS_max_concurrency;
     options.internal_port = FLAGS_internal_port;