Design Configuration Management Service
Design and implement a single-threaded, in-memory configuration management service. Users can submit configuration updates, commit them in FIFO order, and allow subscribers to retrieve the latest committed values.
Every configuration update contains a unique caller-provided update ID, a non-empty configuration key, and a non-empty value. Each subscriber may be subscribed to at most one configuration key at a time.
Method Signatures
Create the Service
ConfigurationManagementService()
- Creates an empty in-memory configuration management service.
- Initially, no key has a committed configuration value.
Subscribe to a Configuration
boolean subscribe(String subscriberId, String key)
- Associates the subscriber with the specified configuration key.
- Returns
true when the subscription is added.
- Returns
false if the subscriber already has an active subscription.
- A subscriber may be subscribed to at most one key at a time.
- If the key already has a committed value, that value becomes immediately available to the subscriber through
getConfigurationUpdate.
- If the key has no committed value,
getConfigurationUpdate returns an empty string until an update for the key is committed.
Unsubscribe from a Configuration
boolean unsubscribe(String subscriberId, String key)
- Removes the subscriber's association with the specified configuration key.
- Returns
true when the matching subscription is removed.
- Returns
false if the subscriber is not subscribed to the specified key.
- After unsubscribing, the subscriber can no longer retrieve updates for that key.
- The subscriber may subscribe to the same key or a different key later.
Submit a Configuration Update
String submitConfigurationUpdate(String updateId, String key, String value)
- Creates a pending configuration update using the provided update ID, key, and value.
- Adds the update to the pending FIFO queue for the key.
- Returns the provided update ID when the update is accepted.
- Returns an empty string if the update ID, key, or value is empty or otherwise invalid.
- Every call is guaranteed to provide an update ID that has never appeared in an earlier call.
- Submitting an update does not immediately change the key's committed value.
Commit a Configuration Update
String configurationUpdatedMethod(String updateId)
- Commits the specified pending configuration update.
- The update must be at the front of the pending FIFO queue for its key.
- The update's value becomes the latest committed value for the key.
- Removes the committed update from its key's pending FIFO queue.
- Subscribers associated with the key immediately observe the newly committed value through
getConfigurationUpdate.
- Returns the committed update ID when successful.
- Returns an empty string if the update does not exist, has already been committed, or is not at the front of its key's pending queue.
Get a Subscriber's Configuration Update
String getConfigurationUpdate(String subscriberId)
- Returns the latest committed value for the key associated with the subscriber.
- Returns an empty string if the subscriber has no active subscription.
- Returns an empty string if the subscriber's key has no committed value.
- A pending or uncommitted value must never be returned.
- Calling this method does not remove, consume, or modify the committed value.
- Repeated calls return the same value until a newer update for the key is committed or the subscriber unsubscribes.
Configuration Update Lifecycle
submitConfigurationUpdate adds an update to the pending FIFO queue for its key.
configurationUpdatedMethod commits the update at the front of that queue.
- The committed value becomes the latest committed value for the key.
- Every subscriber currently associated with the key can retrieve that value through
getConfigurationUpdate.
FIFO Processing Rules
- Each configuration key has an independent pending FIFO queue.
- Only the update at the front of a key's queue may be committed.
- An update cannot be committed while an earlier update for the same key is pending.
- After the front update is committed, the next pending update becomes eligible for commitment.
- Updates for different keys use independent pending queues and may be committed independently.
- Subscribers do not maintain FIFO queues of committed updates.
- Only the latest committed value for each key is available to subscribers.
Subscription Rules
- Each subscriber may have at most one active subscription.
- A subscriber immediately observes the current committed value for its key when the subscription is added, if a committed value exists.
- A subscriber added after an update is submitted but before it is committed observes that value after the update is committed.
- If several updates are committed before the subscriber calls
getConfigurationUpdate, only the latest committed value is returned.
- After unsubscribing,
getConfigurationUpdate returns an empty string for that subscriber.
- Resubscribing makes the latest committed value of the newly subscribed key immediately available.
Constraints
1 ≤ updateId.length() ≤ 200
1 ≤ subscriberId.length() ≤ 200
1 ≤ key.length() ≤ 200
1 ≤ value.length() ≤ 1,000
- The service may contain at most
100,000 keys.
- The service may receive at most
1,000,000 updates.
- A key may have at most
100,000 pending updates.
- A key may have at most
10,000 subscribers.
- Each subscriber may be associated with at most one key.
- The empty string is reserved for missing or invalid results.
- Old committed update history may be removed, but the latest committed value for each key must be retained.
Examples
Example 1: Commit Updates in FIFO Order
ConfigurationManagementService()
Output: a new empty service
subscribe(subscriberId = "api-node", key = "logging.level")
Output: true
submitConfigurationUpdate(updateId = "logging-info", key = "logging.level", value = "INFO")
Output: "logging-info"
submitConfigurationUpdate(updateId = "logging-debug", key = "logging.level", value = "DEBUG")
Output: "logging-debug"
getConfigurationUpdate(subscriberId = "api-node")
Output: ""
configurationUpdatedMethod(updateId = "logging-debug")
Output: ""
configurationUpdatedMethod(updateId = "logging-info")
Output: "logging-info"
getConfigurationUpdate(subscriberId = "api-node")
Output: "INFO"
getConfigurationUpdate(subscriberId = "api-node")
Output: "INFO"
configurationUpdatedMethod(updateId = "logging-debug")
Output: "logging-debug"
getConfigurationUpdate(subscriberId = "api-node")
Output: "DEBUG"
Example 2: Current Value on Subscription and Unsubscription
ConfigurationManagementService()
Output: a new empty service
submitConfigurationUpdate(updateId = "region-india", key = "service.region", value = "india")
Output: "region-india"
configurationUpdatedMethod(updateId = "region-india")
Output: "region-india"
subscribe(subscriberId = "service-node", key = "service.region")
Output: true
getConfigurationUpdate(subscriberId = "service-node")
Output: "india"
subscribe(subscriberId = "service-node", key = "retry.limit")
Output: false
submitConfigurationUpdate(updateId = "region-global", key = "service.region", value = "global")
Output: "region-global"
configurationUpdatedMethod(updateId = "region-global")
Output: "region-global"
getConfigurationUpdate(subscriberId = "service-node")
Output: "global"
unsubscribe(subscriberId = "service-node", key = "service.region")
Output: true
getConfigurationUpdate(subscriberId = "service-node")
Output: ""
subscribe(subscriberId = "service-node", key = "service.region")
Output: true
getConfigurationUpdate(subscriberId = "service-node")
Output: "global"