はじめに
現代の業務システム開発では、似たようなクエリ・集約・フィルタ・組み合わせ処理を
モジュールごとに何度も実装するケースが多くあります。
しかし、これらの処理はビジネスの文脈によって微妙に異なるため、再利用が難しく、
結果として重複コードや保守コストが増大しています。
combie-go は、こうした「似ているけど同じではない処理」を
一つの統一的な仕組みで再利用できるようにするためのライブラリです。
課題と動機:なぜ新しい仕組みが必要なのか
多くのプロジェクトでは、データ処理のロジックを統一的に表現する仕組みがありません。
- ビジネスロジックがコードにベタ書きされ、再利用や拡張が難しい
- 集約関数やフィルタ条件、コンテキスト情報が各層に分散している
- 仕様変更のたびに再テスト・再デプロイが必要になる
こうした問題から、「データをどう処理するか」を都度書くのではなく、「データをどう扱うべきか」を宣言的に表現できる柔軟な仕組みが必要だと感じました。
解決策:combie-go の設計思想
combie-go は、こうした課題を解決するために設計されたコンポーネントです。
Java 開発で得た類似コンポーネントの経験をもとに、処理ロジックを関数として抽象化し、
その関数を構造体の Tag で宣言的に紐付けることで、
最小限のコードで集約・結合・加工処理を実現できます。
combie-go の目指すところは:
- 統一的なデータ処理インターフェースにより、重複コードを削減する
- Tag とコンテキストを活用して拡張性を高める
- 複雑な業務データの流れをシンプルで明確にし、保守性とテスト容易性を両立する
コアバリュー
- 開発時間の短縮:複雑な処理を毎回手書きする必要がない
- 再利用性の向上:関数とロジックを統一管理し、再利用可能に
- 可読性・メンテナンス性の向上:統一 API によりロジックの見通しを向上
インストールとクイックスタート
go get github.com/kyousukesan/combie-go
推奨 Go バージョン: 1.22+
package main
import (
"fmt"
"strings"
combine "github.com/kyousukesan/combie-go"
)
type Item struct {
ID int `combine:"combineItem,Items"`
Name string `combine:"uppercase,Name"`
Score int `combine:"avg_score,fn:SetAvg"`
Items []string
AvgScore float64
}
func (i *Item) SetAvg(v any) {
switch x := v.(type) {
case float64:
i.AvgScore = x
case int:
i.AvgScore = float64(x)
}
}
func AvgScoreAgg(values []any, ctx map[string]any) map[any]any {
out := make(map[any]any, len(values))
for idx := range values {
if idx == 0 {
out[idx] = float64(20)
} else {
out[idx] = float64(100)
}
}
return out
}
func main() {
// コンテキスト。処理関数内で利用可能
b := combine.NewCtxBuilder().
Set("factor", 1.5).
Set("env", "prod").
Set("region", "ap-northeast-1").
Set("trace", true)
// 初期化。デフォルトで並列処理を有効化し、コンテキストを設定
c := combine.NewCombine(
combine.WithConcurrent(),
combine.WithCtxBuilder(b),
)
// ハンドラ関数を登録。入力:values []any, ctx map[string]any、出力:map[any]any
c.Register("uppercase", combine.HandleFunc(func(values []any, ctx map[string]any) map[any]any {
out := make(map[any]any, len(values))
for idx, v := range values {
out[idx] = strings.ToUpper(fmt.Sprint(v))
}
return out
}))
c.Register("combineItem", combine.HandleFunc(func(values []any, ctx map[string]any) map[any]any {
out := make(map[any]any, len(values))
for idx, v := range values {
if id, _ := v.(int); id == 1 {
out[idx] = []string{"bbb", "aaa", "ccc"}
} else {
out[idx] = []string{"sss"}
}
}
return out
}))
// 再利用可能な処理関数を登録。データモデルと紐付けて、どこでも利用できます
c.Register("avg_score", combine.HandleFunc(AvgScoreAgg))
items := []Item{
{ID: 1, Name: "alice", Score: 90},
{ID: 2, Name: "bob", Score: 80},
}
// 処理を実行。結果の map を元データにマッピング
if err := c.Process(items); err != nil {
panic(err)
}
fmt.Printf("%+v\n", items)
}
コアコンセプトと設計
-
汎用データ処理:
[]structまたは[]*structに対応し、リフレクションで自動的にフィールドを識別 -
Tag ベース集約:
combineタグで集約関数と出力方法を指定 -
コンテキスト対応:
NewCtxBuilderによるチェーン構築で、登録関数からcombineCtxにアクセス可能 -
並列実行:
WithConcurrent()で複数の集約関数を並列処理 - 拡張性:カスタム集約関数を登録可能、様々な業務シナリオに対応
combine タグの構文
combine タグは、集約関数 と フィールド出力方法 を指定します。基本形式は以下の通りです:
`combine:"集約関数名,フィールド名[,fn:Method]"`
構成要素
-
集約関数名
-
Combine.Register()で登録した関数名 - 例:
uppercase、combineItem、avg_score
-
-
フィールド名
- 集約結果を書き込む構造体フィールド
- 例:
Name、Items
-
fn メソッド(任意)
- フィールドに直接書き込むのではなく、メソッド経由で出力
- 例:
fn:SetAvg
例
type Item struct {
ID int `combine:"combineItem,Items"` // combineItem の結果を Items フィールドに反映
Name string `combine:"uppercase,Name"` // uppercase の結果を Name に書き込む
Score int `combine:"avg_score,fn:SetAvg"` // avg_score の結果を SetAvg メソッドに渡す
Items []string
AvgScore float64
}
補足
- タグは コンポーネント駆動の核 であり、どのフィールドがどの関数で処理されるかを決定
- 直接フィールド書き込み または 出力メソッド呼び出し(fn) の両方に対応
- 集約関数内から
Combineコンテキスト (combineCtx) にアクセス可能で、柔軟なロジックが実現可能
API(現行バージョン)
// Combine の生成
func NewCombine(opts ...Option) *Combine
// オプション
func WithConcurrent() Option // 集約関数の並列実行を有効化,並行実行時に安全である必要があります
func WithCtx(ctx map[string]any) Option // コンポーネントコンテキスト combineCtx を設定
func WithCtxBuilder(b *CtxBuilder) Option // CtxBuilder で複数 k-v を設定
// 集約関数登録
type AggregateHandler interface { Handle(values []any, combineCtx map[string]any) map[any]any }
type HandleFunc func(values []any, combineCtx map[string]any) map[any]any // adapter
func (c *Combine) Register(name string, fn AggregateHandler)
func (c *Combine) RegisterAggregate(name string, fn AggregateHandler)
// 実行
func (c *Combine) Process(items []any) error
今後の計画
- 複雑な集約関数のサポート(グループ化・条件式対応)
- キャッシュ層の実装(重複計算の抑制と性能最適化)
- ソート・ランキング機能 の導入
- ネストされた struct の集約への完全対応 への完全対応
サンプルとテスト
go run ./example
go test ./...
