Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions mooncake-store/include/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,34 @@ class Client {
UUID client_id_;
};

/**
* @brief Fluent builder for configuring a mooncake::Client instance.
*
* Provides readable, type-safe setters with sensible defaults so callers can
* specify only the options they need while reusing existing Client::Create
* logic under the hood.
*/
class MooncakeStoreBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ClientBuilder should be a better name in the context, as we are infact building a Client, not a MooncakeStore.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about MooncakeStoreClientBuilder? Don't be afraid of a long name.

public:
MooncakeStoreBuilder& WithLocalHostname(std::string local_hostname);
MooncakeStoreBuilder& WithMetadataConnectionString(
std::string metadata_connstring);
MooncakeStoreBuilder& UsingProtocol(std::string protocol);
MooncakeStoreBuilder& WithRdmaDeviceNames(std::string device_names);
MooncakeStoreBuilder& WithMasterServerEntry(
std::string master_server_entry);
MooncakeStoreBuilder& WithExistingTransferEngine(
std::shared_ptr<TransferEngine> transfer_engine);

[[nodiscard]] std::optional<std::shared_ptr<Client>> Build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Build() method does not modify the state of the MooncakeStoreBuilder. It's good practice to mark such methods as const. This improves API correctness by clearly stating the method's intent and allows it to be called on const builder instances. You'll also need to update the implementation in client.cpp.

Suggested change
[[nodiscard]] std::optional<std::shared_ptr<Client>> Build();
[[nodiscard]] std::optional<std::shared_ptr<Client>> Build() const;


private:
std::optional<std::string> local_hostname_;
std::optional<std::string> metadata_connstring_;
std::string protocol_ = "tcp";
std::optional<std::string> device_names_;
std::string master_server_entry_ = kDefaultMasterAddress;
std::shared_ptr<TransferEngine> transfer_engine_ = nullptr;
};

} // namespace mooncake
1 change: 1 addition & 0 deletions mooncake-store/include/pybind_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class PyClient {
// Factory to create shared instances and auto-register to ResourceTracker
static std::shared_ptr<PyClient> create();

[[deprecated("Use MooncakeStoreBuilder instead")]]
int setup(const std::string &local_hostname,
const std::string &metadata_server,
size_t global_segment_size = 1024 * 1024 * 16,
Expand Down
69 changes: 69 additions & 0 deletions mooncake-store/src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <string_view>
#include <optional>
#include <ranges>
#include <thread>
Expand Down Expand Up @@ -406,6 +407,74 @@ std::optional<std::shared_ptr<Client>> Client::Create(
return client;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithLocalHostname(
std::string local_hostname) {
local_hostname_ = std::move(local_hostname);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use const std::string& instead of std::string for the parameter, and don't move the parameter. Please also update the other "WithXXX" methods that taking strings as arguments.

return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithMetadataConnectionString(
std::string metadata_connstring) {
metadata_connstring_ = std::move(metadata_connstring);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::UsingProtocol(
std::string protocol) {
protocol_ = std::move(protocol);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithRdmaDeviceNames(
std::string device_names) {
if (device_names.empty()) {
device_names_.reset();
} else {
device_names_ = std::move(device_names);
}
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithMasterServerEntry(
std::string master_server_entry) {
master_server_entry_ = std::move(master_server_entry);
return *this;
}

MooncakeStoreBuilder& MooncakeStoreBuilder::WithExistingTransferEngine(
std::shared_ptr<TransferEngine> transfer_engine) {
transfer_engine_ = std::move(transfer_engine);
return *this;
}

std::optional<std::shared_ptr<Client>> MooncakeStoreBuilder::Build() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To match the const qualifier added to the declaration in the header file, the implementation of Build() should also be marked as const.

Suggested change
std::optional<std::shared_ptr<Client>> MooncakeStoreBuilder::Build() {
std::optional<std::shared_ptr<Client>> MooncakeStoreBuilder::Build() const {

std::vector<std::string_view> missing;
if (!local_hostname_) {
missing.emplace_back("local_hostname");
}
if (!metadata_connstring_) {
missing.emplace_back("metadata_connstring");
}

if (!missing.empty()) {
std::string joined;
joined.reserve(missing.size() * 16);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reserve is not necessary because this is not a performance critical path and 16 is a magic number. It's fine to let the string allocate space as needed.

for (size_t i = 0; i < missing.size(); ++i) {
if (i != 0) {
joined.append(", ");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This for loop and the if (i != 0) is redundent and confusing, just remove them.

joined.append(missing[i]);
}
Comment on lines 459 to 468
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This loop for joining strings with a separator can be made slightly more efficient and idiomatic by handling the first element outside the loop. This avoids the conditional check if (i != 0) in every iteration.

        joined.append(missing[0]);
        for (size_t i = 1; i < missing.size(); ++i) {
            joined.append(", ");
            joined.append(missing[i]);
        }

LOG(ERROR) << "MooncakeStoreBuilder missing required fields: "
<< joined;
return std::nullopt;
}

return Client::Create(*local_hostname_, *metadata_connstring_, protocol_,
device_names_, master_server_entry_,
transfer_engine_);
}

tl::expected<void, ErrorCode> Client::Get(const std::string& object_key,
std::vector<Slice>& slices) {
auto query_result = Query(object_key);
Expand Down
17 changes: 14 additions & 3 deletions mooncake-store/src/pybind_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,20 @@ tl::expected<void, ErrorCode> PyClient::setup_internal(
(rdma_devices.empty() ? std::nullopt
: std::make_optional(rdma_devices));

auto client_opt = mooncake::Client::Create(
this->local_hostname, metadata_server, protocol, device_name,
master_server_addr, transfer_engine);
MooncakeStoreBuilder builder;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert this? just use create is good enough i think

builder.WithLocalHostname(this->local_hostname)
.WithMetadataConnectionString(metadata_server)
.UsingProtocol(protocol)
.WithMasterServerEntry(master_server_addr);

if (device_name) {
builder.WithRdmaDeviceNames(*device_name);
}
if (transfer_engine) {
builder.WithExistingTransferEngine(transfer_engine);
}

auto client_opt = builder.Build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The builder pattern is most powerful when its methods are chained fluently. The current implementation can be simplified into a single, more readable statement. The if conditions for optional parameters can be avoided by using std::optional::value_or for device_name and by passing the (potentially null) transfer_engine shared_ptr directly, as the builder handles these cases gracefully.

    auto client_opt = MooncakeStoreBuilder()
        .WithLocalHostname(this->local_hostname)
        .WithMetadataConnectionString(metadata_server)
        .UsingProtocol(protocol)
        .WithMasterServerEntry(master_server_addr)
        .WithRdmaDeviceNames(device_name.value_or(""))
        .WithExistingTransferEngine(transfer_engine)
        .Build();

if (!client_opt) {
LOG(ERROR) << "Failed to create client";
return tl::unexpected(ErrorCode::INVALID_PARAMS);
Expand Down
Loading