adding-a-configuration-property by microbus-io
Creates or modify a configuration property of a microservice. Use when explicitly asked by the user to create or modify a configuration property of a microservice, or when it makes sense to externalize a certain setting of the microservice.
Content & Writing
169 Stars
5 Forks
Updated Dec 14, 2025, 05:54 PM
Why Use This
This skill provides specialized capabilities for microbus-io's codebase.
Use Cases
- Developing new features in the microbus-io repository
- Refactoring existing code to follow microbus-io standards
- Understanding and working with microbus-io's codebase structure
Install Guide
2 steps- 1
Skip this step if Ananke is already installed.
- 2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 361 Lines
Total Files 1
Total Size 0 B
License NOASSERTION
---
name: Adding a Configuration Property
description: Creates or modify a configuration property of a microservice. Use when explicitly asked by the user to create or modify a configuration property of a microservice, or when it makes sense to externalize a certain setting of the microservice.
---
**CRITICAL**: Do NOT explore or analyze other microservices unless explicitly instructed to do so. The instructions in this skill are self-contained to this microservice.
**CRITICAL**: Do not omit the `MARKER` comments when generating the code. They are intended as waypoints for future edits.
## Workflow
Copy this checklist and track your progress:
```
Creating or modifying a configuration property:
- [ ] Step 1: Read local AGENTS.md file
- [ ] Step 2: Determine type
- [ ] Step 3: Determine properties
- [ ] Step 4: Extend the ToDo interface
- [ ] Step 5: Define the config
- [ ] Step 6: Implement the getter and setter
- [ ] Step 7: Wire up the config change dispatcher
- [ ] Step 8: Implement the callback
- [ ] Step 9: Use the config
- [ ] Step 10: Extend the mock
- [ ] Step 11: Test the callback
- [ ] Step 12: Add to config file
- [ ] Step 13: Housekeeping
```
#### Step 1: Read Local `AGENTS.md` File
Read the local `AGENTS.md` file in the microservice's directory. It contains microservice-specific instructions that should take precedence over global instructions.
#### Step 2: Determine Type
Determine the return type of the configuration property. It must be one of:
- `string`
- `int`
- `bool`
- `float64`
- `time.Duration`
#### Step 3: Determine Properties
Determine the properties of the configuration property:
- **Description**: explains the purpose of the property. It should start with the name of the property
- **Default value**: a default value for the property (optional)
- **Validation**: an optional validation rule (see below)
- **Secret**: whether the value is a secret that should not be logged
- **Callback**: whether a callback should be triggered when the value changes, for example to reopen a connection to an external resource
Validation rules can be any of the following:
- `str` followed by a regexp: `str ^[a-zA-Z0-9]+$`
- `bool`
- `int` followed by an open, closed or mixed interval: `int [0,60]`
- `float` followed by an open, closed or mixed interval: `float [0.0,1.0)`
- `dur` followed by an open, closed or mixed interval of Go durations: `dur (0s,24h]`
- `set` followed by a pipe-separated list of values: `set Red|Green|Blue`
- `url`
- `email`
- `json`
#### Step 4: Extend the `ToDo` Interface
Skip this step if the config does not have a callback.
Extend the `ToDo` interface in `intermediate.go`.
```go
type ToDo interface {
// ...
OnChangedMyConfig(ctx context.Context) (err error) // MARKER: MyConfig
}
```
#### Step 5: Define the Config
Define the configuration property in `NewIntermediate` in `intermediate.go`, after the corresponding `HINT` comment.
- Include `cfg.Description`, `cfg.DefaultValue`, `cfg.Validation`, and `cfg.Secret` as appropriate
- Omit options that are empty or false
- The config name is PascalCase
- In `cfg.Description`, replace `MyConfig is X` with the description of the configuration property
```go
func NewIntermediate(impl ToDo) *Intermediate {
// ...
svc.DefineConfig( // MARKER: MyConfig
"MyConfig",
cfg.Description(`MyConfig is X.`),
cfg.DefaultValue("1"),
cfg.Validation("int (1,100]"),
cfg.Secret(),
)
}
```
#### Step 6: Implement the Getter and Setter
Append the getter and setter methods to `intermediate.go`.
- Set an appropriate comment describing the config property
- The getter converts the string value to the appropriate type
- The setter converts the value to a string using type-specific code
If the config type is `string`:
```go
/*
MyConfig is X.
*/
func (svc *Intermediate) MyConfig() (value string) { // MARKER: MyConfig
return svc.Config("MyConfig")
}
/*
SetMyConfig sets the value of the configuration property.
*/
func (svc *Intermediate) SetMyConfig(value string) (err error) { // MARKER: MyConfig
return svc.SetConfig("MyConfig", value)
}
```
If the config type is `int`:
```go
/*
MyConfig is X.
*/
func (svc *Intermediate) MyConfig() (value int) { // MARKER: MyConfig
_val := svc.Config("MyConfig")
_i, _ := strconv.ParseInt(_val, 10, 64)
return int(_i)
}
/*
SetMyConfig sets the value of the configuration property.
*/
func (svc *Intermediate) SetMyConfig(value int) (err error) { // MARKER: MyConfig
return svc.SetConfig("MyConfig", strconv.Itoa(value))
}
```
If the config type is `bool`:
```go
/*
MyConfig is X.
*/
func (svc *Intermediate) MyConfig() (value bool) { // MARKER: MyConfig
_val := svc.Config("MyConfig")
_b, _ := strconv.ParseBool(_val)
return _b
}
/*
SetMyConfig sets the value of the configuration property.
*/
func (svc *Intermediate) SetMyConfig(value bool) (err error) { // MARKER: MyConfig
return svc.SetConfig("MyConfig", strconv.FormatBool(value))
}
```
If the config type is `float64`:
```go
/*
MyConfig is X.
*/
func (svc *Intermediate) MyConfig() (value float64) { // MARKER: MyConfig
_val := svc.Config("MyConfig")
_f, _ := strconv.ParseFloat(_val, 64)
return _f
}
/*
SetMyConfig sets the value of the configuration property.
*/
func (svc *Intermediate) SetMyConfig(value float64) (err error) { // MARKER: MyConfig
return svc.SetConfig("MyConfig", strconv.FormatFloat(value, 'f', -1, 64))
}
```
If the config type is `time.Duration`:
```go
/*
MyConfig is X.
*/
func (svc *Intermediate) MyConfig() (value time.Duration) { // MARKER: MyConfig
_val := svc.Config("MyConfig")
_dur, _ := time.ParseDuration(_val)
return _dur
}
/*
SetMyConfig sets the value of the configuration property.
*/
func (svc *Intermediate) SetMyConfig(value time.Duration) (err error) { // MARKER: MyConfig
return svc.SetConfig("MyConfig", value.String())
}
```
#### Step 7: Wire Up the Config Change Dispatcher
Skip this step if the config does not have a callback.
Add a dispatch entry in `doOnConfigChanged` in `intermediate.go`.
```go
// doOnConfigChanged is called when the config of the microservice changes.
func (svc *Intermediate) doOnConfigChanged(ctx context.Context, changed func(string) bool) (err error) {
// ...
if changed("MyConfig") { // MARKER: MyConfig
err = svc.OnChangedMyConfig(ctx)
}
return err
}
```
#### Step 8: Implement the Callback
Skip this step if the config does not have a callback.
Define a callback in `service.go` that handles the change.
```go
func (svc *Service) OnChangedMyConfig(ctx context.Context) (err error) { // MARKER: MyConfig
// Implement handling of the new value here
return nil
}
```
#### Step 9: Use the Config
The config itself does not have an implementation. Rather, read its value from within other endpoints using its getter.
```go
myConfig := svc.MyConfig()
```
Use `svc.SetMyConfig(value)` to set the value programmatically, for example in tests.
#### Step 10: Extend the Mock
Skip this step if the config does not have a callback.
Add a field to the `Mock` structure definition in `mock.go` to hold a mock handler.
```go
type Mock struct {
// ...
mockOnChangedMyConfig func(ctx context.Context) (err error) // MARKER: MyConfig
}
```
Add the stub to the `Mock`.
```go
// MockOnChangedMyConfig sets up a mock handler for OnChangedMyConfig.
func (svc *Mock) MockOnChangedMyConfig(handler func(ctx context.Context) (err error)) *Mock { // MARKER: MyConfig
svc.mockOnChangedMyConfig = handler
return svc
}
// OnChangedMyConfig executes the mock handler.
func (svc *Mock) OnChangedMyConfig(ctx context.Context) (err error) { // MARKER: MyConfig
if svc.mockOnChangedMyConfig == nil {
err = errors.New("mock not implemented", http.StatusNotImplemented)
return
}
err = svc.mockOnChangedMyConfig(ctx)
return errors.Trace(err)
}
```
Add a test case in `TestMyService_Mock`.
```go
t.Run("on_changed_my_config", func(t *testing.T) { // MARKER: MyConfig
assert := testarossa.For(t)
err := mock.OnChangedMyConfig(ctx)
assert.Contains(err.Error(), "not implemented")
mock.MockOnChangedMyConfig(func(ctx context.Context) (err error) {
return nil
})
err = mock.OnChangedMyConfig(ctx)
assert.NoError(err)
})
```
#### Step 11: Test the Callback
Skip this step if the config does not have a callback.
Append the integration test to `service_test.go`.
```go
func TestMyService_OnChangedMyConfig(t *testing.T) { // MARKER: MyConfig
t.Parallel()
ctx := t.Context()
_ = ctx
// Initialize the microservice under test
svc := NewService()
// Run the testing app
app := application.New()
app.Add(
// HINT: Add microservices or mocks required for this test
svc,
)
app.RunInTest(t)
/*
HINT: Use the following pattern for each test case
t.Run("test_case_name", func(t *testing.T) {
assert := testarossa.For(t)
err := svc.SetMyConfig(newValue)
assert.NoError(err)
})
*/
}
```
Skip the remainder of this step if instructed to be "quick" or to skip tests.
Insert test cases at the bottom of the integration test function using the recommended pattern.
```go
t.Run("test_case_name", func(t *testing.T) {
assert := testarossa.For(t)
err := svc.SetMyConfig(newValue)
assert.NoError(err)
})
```
Do not remove the `HINT` comments.
#### Step 12: Add to Config File
Add a commented-out entry for the new configuration property to the appropriate config file at the root of the project, nested under the hostname of the microservice. Use the default value if one was defined, or leave it blank otherwise.
If the config is secret, add it to `config.local.yaml`. If the config is not secret, add it to `config.yaml`. Create the file if it does not exist.
If a section for the hostname already exists in the file, add the new property to that section. Otherwise, create a new section.
```yaml
my.service.hostname:
# MyConfig: default
```
#### Step 13: Housekeeping
Follow the `microbus/housekeeping` skill.
Name Size