Repository: honeydipper/honeydipper
Pull request #533 (2023-04-07 21:07:46)

chore(release): release
Highlights:

* feat: loading files matching glob


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f818f23..856e8f0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -95,7 +95,7 @@ jobs:
     <<: *defaults
     docker:
       - image: golang:1.18.10-alpine
-      - image: redis:7.0.9-alpine
+      - image: redis:7.0.10-alpine
     steps:
       - attach_workspace:
           at: /go
diff --git a/go.mod b/go.mod
index ace11fd..f51d5e3 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.18
 
 require (
 	cloud.google.com/go v0.110.0 // indirect
-	cloud.google.com/go/pubsub v1.28.0
+	cloud.google.com/go/pubsub v1.30.0
 	cloud.google.com/go/spanner v1.44.0
 	cloud.google.com/go/storage v1.28.1
 	github.com/DataDog/datadog-go v4.8.3+incompatible
@@ -17,11 +17,11 @@ require (
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/gogf/gf v1.16.9
 	github.com/golang/mock v1.6.0
-	github.com/golang/protobuf v1.5.2
+	github.com/golang/protobuf v1.5.3
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/uuid v1.3.0
-	github.com/googleapis/gax-go/v2 v2.7.0
+	github.com/googleapis/gax-go/v2 v2.7.1
 	github.com/huandu/xstrings v1.4.0 // indirect
 	github.com/imdario/mergo v0.3.13
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -34,12 +34,12 @@ require (
 	github.com/qiangmzsx/string-adapter/v2 v2.1.0
 	github.com/stretchr/testify v1.8.1
 	golang.org/x/crypto v0.6.0
-	golang.org/x/net v0.7.0 // indirect
-	golang.org/x/oauth2 v0.5.0
-	golang.org/x/term v0.5.0
-	golang.org/x/text v0.7.0 // indirect
-	google.golang.org/api v0.110.0
-	google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488
+	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/oauth2 v0.6.0
+	golang.org/x/term v0.6.0
+	golang.org/x/text v0.8.0 // indirect
+	google.golang.org/api v0.114.0
+	google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd
 	gopkg.in/h2non/gock.v1 v1.0.15
 	gopkg.in/src-d/go-git.v4 v4.13.1
 	k8s.io/api v0.24.2
@@ -49,7 +49,7 @@ require (
 )
 
 require (
-	cloud.google.com/go/kms v1.8.0
+	cloud.google.com/go/kms v1.10.0
 	cloud.google.com/go/secretmanager v1.10.0
 	github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
 	github.com/Microsoft/go-winio v0.5.1 // indirect
@@ -92,10 +92,10 @@ require (
 	go.opencensus.io v0.24.0 // indirect
 	go.opentelemetry.io/otel v1.3.0 // indirect
 	go.opentelemetry.io/otel/trace v1.3.0 // indirect
-	golang.org/x/time v0.1.0 // indirect
+	golang.org/x/time v0.3.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/grpc v1.53.0 // indirect
-	google.golang.org/protobuf v1.28.1 // indirect
+	google.golang.org/grpc v1.54.0 // indirect
+	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
@@ -114,15 +114,15 @@ require (
 )
 
 require (
-	cloud.google.com/go/compute v1.18.0 // indirect
+	cloud.google.com/go/compute v1.19.0 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
-	cloud.google.com/go/iam v0.12.0 // indirect
+	cloud.google.com/go/iam v0.13.0 // indirect
 	cloud.google.com/go/longrunning v0.4.1 // indirect
 	github.com/Masterminds/semver/v3 v3.2.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/spf13/cast v1.5.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 )
diff --git a/go.sum b/go.sum
index ef03255..3ab96cb 100644
--- a/go.sum
+++ b/go.sum
@@ -32,6 +32,8 @@ cloud.google.com/go/compute v1.13.0 h1:AYrLkB8NPdDRslNp4Jxmzrhdr03fUAIDbiGFjLWow
 cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
 cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY=
 cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
+cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
+cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
 cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
@@ -42,8 +44,12 @@ cloud.google.com/go/iam v0.10.0 h1:fpP/gByFs6US1ma53v7VxhvbJpO2Aapng6wabJ99MuI=
 cloud.google.com/go/iam v0.10.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM=
 cloud.google.com/go/iam v0.12.0 h1:DRtTY29b75ciH6Ov1PHb4/iat2CLCvrOm40Q0a6DFpE=
 cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=
+cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k=
+cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
 cloud.google.com/go/kms v1.8.0 h1:VrJLOsMRzW7IqTTYn+OYupqF3iKSE060Nrn+PECrYjg=
 cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
+cloud.google.com/go/kms v1.10.0 h1:Imrtp8792uqNP9bdfPrjtUkjjqOMBcAJ2bdFaAnLhnk=
+cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=
 cloud.google.com/go/longrunning v0.4.0 h1:v+X4EwhHl6xE+TG1XgXj4T1XpKKs7ZevcAJ3FOu0YmY=
 cloud.google.com/go/longrunning v0.4.0/go.mod h1:eF3Qsw58iX/bkKtVjMTYpH0LRjQ2goDkjkNQTlzq/ZM=
 cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
@@ -56,6 +62,8 @@ cloud.google.com/go/pubsub v1.27.1 h1:q+J/Nfr6Qx4RQeu3rJcnN48SNC0qzlYzSeqkPq93VH
 cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=
 cloud.google.com/go/pubsub v1.28.0 h1:XzabfdPx/+eNrsVVGLFgeUnQQKPGkMb8klRCeYK52is=
 cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=
+cloud.google.com/go/pubsub v1.30.0 h1:vCge8m7aUKBJYOgrZp7EsNDf6QMd2CAlXZqWTn3yq6s=
+cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=
 cloud.google.com/go/secretmanager v1.10.0 h1:pu03bha7ukxF8otyPKTFdDz+rr9sE3YauS5PliDXK60=
 cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=
 cloud.google.com/go/spanner v1.42.0 h1:W6RIkN4X0htp+GRNWK1rhXYYcjb5znZDuyIO2CWkoM0=
@@ -245,6 +253,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
 github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
 github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -299,6 +309,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
 github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
+github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
+github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -582,6 +594,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -598,6 +612,8 @@ golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqi
 golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
 golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
 golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -672,11 +688,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -689,12 +709,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
 golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -780,6 +804,8 @@ google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
 google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
 google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU=
 google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
+google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
+google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -832,6 +858,8 @@ google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 h1:vArvWooPH749rNH
 google.golang.org/genproto v0.0.0-20230202175211-008b39050e57/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
 google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488 h1:QQF+HdiI4iocoxUjjpLgvTYDHKm99C/VtTBFnfiCJos=
 google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=
+google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
+google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -854,6 +882,8 @@ google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
 google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
 google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
 google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
+google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -869,6 +899,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/api/response.go b/internal/api/response.go
index 38f6f0e..a513f09 100644
--- a/internal/api/response.go
+++ b/internal/api/response.go
@@ -8,7 +8,6 @@ package api
 
 import (
 	"fmt"
-	"io"
 	"strconv"
 
 	"github.com/honeydipper/honeydipper/pkg/dipper"
@@ -22,14 +21,14 @@ const DefaultAPILockExpireMS = 1000
 
 // Response is used for responding to the api service.
 type Response struct {
-	EventBus io.Writer
+	EventBus dipper.MessageReceiver
 	Request  *dipper.Message
 	Acked    bool
 }
 
 // Ack acks a call.
 func (resp *Response) Ack() {
-	dipper.SendMessage(resp.EventBus, &dipper.Message{
+	resp.EventBus.SendMessage(&dipper.Message{
 		Channel: "eventbus",
 		Subject: "api",
 		Labels: map[string]string{
@@ -43,7 +42,7 @@ func (resp *Response) Ack() {
 
 // Return returns data to api service.
 func (resp *Response) Return(data interface{}) {
-	dipper.SendMessage(resp.EventBus, &dipper.Message{
+	resp.EventBus.SendMessage(&dipper.Message{
 		Channel: "eventbus",
 		Subject: "api",
 		Labels: map[string]string{
@@ -57,7 +56,7 @@ func (resp *Response) Return(data interface{}) {
 
 // ReturnError returns an error to the API service.
 func (resp *Response) ReturnError(err error) {
-	dipper.SendMessage(resp.EventBus, &dipper.Message{
+	resp.EventBus.SendMessage(&dipper.Message{
 		Channel: "eventbus",
 		Subject: "api",
 		Labels: map[string]string{
@@ -94,7 +93,7 @@ func NewResponseFactory() *ResponseFactory {
 }
 
 // NewResponse provides a function to create new api Response.
-func (rf *ResponseFactory) NewResponse(caller dipper.RPCCaller, eventbus io.Writer, m *dipper.Message) *Response {
+func (rf *ResponseFactory) NewResponse(caller dipper.RPCCaller, eventbus dipper.MessageReceiver, m *dipper.Message) *Response {
 	resp := &Response{
 		EventBus: eventbus,
 		Request:  m,
diff --git a/internal/config/file_reference.go b/internal/config/file_reference.go
index 574b6b2..84c5387 100644
--- a/internal/config/file_reference.go
+++ b/internal/config/file_reference.go
@@ -20,7 +20,7 @@ import (
 var ErrFileError = errors.New("file error")
 
 func (c *Repo) normalizeFilePath(cwd string, file string) string {
-	fullpath := path.Clean(path.Join(c.root, cwd, file))
+	fullpath := path.Clean(path.Join(cwd, file))
 	if !strings.HasPrefix(fullpath, c.root+"/") {
 		panic(fmt.Errorf("%w: invalid path: %s", ErrFileError, fullpath))
 	}
diff --git a/internal/config/repo.go b/internal/config/repo.go
index 450b5fb..a2e0a92 100644
--- a/internal/config/repo.go
+++ b/internal/config/repo.go
@@ -67,6 +67,22 @@ func (c *Repo) ReadFile(filename string) ([]byte, error) {
 	return b, nil
 }
 
+func (c *Repo) loadFileGlob(globname string) {
+	filenames, err := filepath.Glob(globname)
+	if err != nil {
+		dipper.Logger.Errorf("%v", err)
+		dipper.Logger.Fatalf("Unable to expand glob to filenames in '%s'", globname)
+	}
+
+	if len(filenames) == 0 {
+		dipper.Logger.Warningf("glob '%s'not matching any files", globname)
+	}
+
+	for _, f := range filenames {
+		c.loadFile(f)
+	}
+}
+
 func (c *Repo) loadFile(filename string) {
 	defer c.recovering(filename, "")
 
@@ -75,7 +91,7 @@ func (c *Repo) loadFile(filename string) {
 	}
 
 	var content DataSet
-	yamlFile := dipper.Must(os.ReadFile(path.Join(c.root, filename[1:]))).([]byte)
+	yamlFile := dipper.Must(os.ReadFile(filename)).([]byte)
 	switch ret := dipper.InterpolateGoTemplate(true, "filename", string(yamlFile), map[string]interface{}{"env": dipper.Getenv()}).(type) {
 	case *bytes.Buffer:
 		yamlFile = ret.Bytes()
@@ -98,9 +114,7 @@ func (c *Repo) loadFile(filename string) {
 		cwd := path.Dir(filename)
 		for _, include := range content.Includes {
 			absname := path.Clean(path.Join(cwd, include))
-			if !c.isFileLoaded(absname) {
-				c.loadFile(absname)
-			}
+			c.loadFileGlob(absname)
 		}
 	}
 
@@ -165,7 +179,7 @@ func (c *Repo) loadRepo() {
 	if c.repo.Path != "" {
 		root = c.repo.Path
 	}
-	c.loadFile(path.Clean(path.Join(root, "init.yaml")))
+	c.loadFile(path.Clean(path.Join(c.root, root, "init.yaml")))
 	dipper.Logger.Infof("repo [%v] loaded", c.repo.Repo)
 }
 
@@ -214,7 +228,7 @@ func (c *Repo) refreshRepo() bool {
 	if c.repo.Path != "" {
 		root = c.repo.Path
 	}
-	c.loadFile(path.Clean(path.Join(root, "init.yaml")))
+	c.loadFile(path.Clean(path.Join(c.root, root, "init.yaml")))
 	dipper.Logger.Warningf("repo [%v] reloaded", c.repo.Repo)
 
 	return true
diff --git a/internal/driver/builtin.go b/internal/driver/builtin.go
index 1ea7bd4..6b68e14 100644
--- a/internal/driver/builtin.go
+++ b/internal/driver/builtin.go
@@ -8,20 +8,33 @@ package driver
 
 import (
 	"fmt"
+	"io"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
+
+	"github.com/honeydipper/honeydipper/internal/daemon"
+	"github.com/honeydipper/honeydipper/pkg/dipper"
 )
 
 // BuiltinDriver are compiled and delivered with daemon binary in the same container image.
 type BuiltinDriver struct {
-	meta *Meta
+	meta   *Meta
+	stream chan<- *dipper.Message
+
+	input  io.ReadCloser
+	output io.WriteCloser
+	run    *exec.Cmd
 }
 
 // BuiltinPath is the path where the builtin drivers are kept. It will try using $HONEYDIPPER_DRIVERS_BUILTIN by default.
 // If $HONEYDIPPER_DRIVERS_BUILTIN is not set, will try using $GOPATH/bin.  If $GOPATH is not defined, use "/opt/honeydipper/driver/builtin".
 var BuiltinPath string
 
+// replace the func variable with mock during testing.
+var execCommand = exec.Command
+
 func builtinPath() string {
 	if BuiltinPath == "" {
 		driverPath, ok := os.LookupEnv("HONEYDIPPER_DRIVERS_BUILTIN")
@@ -53,7 +66,9 @@ func (d *BuiltinDriver) Acquire() {
 
 // Prepare function is used for preparing the arguments when calling the executable.
 // for the driver.
-func (d *BuiltinDriver) Prepare() {
+func (d *BuiltinDriver) Prepare(stream chan<- *dipper.Message) {
+	d.stream = stream
+
 	var argsList []interface{}
 
 	args, ok := d.meta.HandlerData["arguments"]
@@ -82,3 +97,70 @@ func (d *BuiltinDriver) Meta() *Meta {
 func NewBuiltinDriver(m *Meta) *BuiltinDriver {
 	return &BuiltinDriver{meta: m}
 }
+
+// Start the driver child process.  The "service" indicates which service this driver belongs to.
+func (d *BuiltinDriver) Start(service string) {
+	d.run = execCommand(d.meta.Executable, append([]string{service}, d.meta.Arguments...)...)
+	if input, err := d.run.StdoutPipe(); err != nil {
+		dipper.Logger.Panicf("[%s] Unable to link to driver stdout %v", service, err)
+	} else {
+		d.input = input
+		go d.fetchMessages(service)
+	}
+
+	if output, err := d.run.StdinPipe(); err != nil {
+		dipper.Logger.Panicf("[%s] Unable to link to driver stdin %v", service, err)
+	} else {
+		d.output = output
+	}
+
+	d.run.Stderr = os.Stderr
+	d.run.ExtraFiles = []*os.File{os.Stdout} // giving child process stdout for logging
+	if err := d.run.Start(); err != nil {
+		dipper.Logger.Panicf("[%s] Failed to start driver %v", service, err)
+	}
+}
+
+// SendMessage sends a dipper message to the driver child process.
+func (d *BuiltinDriver) SendMessage(msg *dipper.Message) {
+	dipper.SendMessage(d.output, msg)
+}
+
+func (d *BuiltinDriver) fetchMessages(service string) {
+	quit := false
+	daemon.Children.Add(1)
+	defer daemon.Children.Done()
+	for !quit && !daemon.ShuttingDown {
+		func() {
+			defer dipper.SafeExitOnError(
+				"failed to fetching messages from driver %s.%s",
+				service,
+				d.meta.Name,
+			)
+			defer dipper.CatchError(io.EOF, func() { quit = true })
+			for !quit && !daemon.ShuttingDown {
+				message := dipper.FetchRawMessage(d.input)
+				d.stream <- message
+			}
+		}()
+	}
+	dipper.Logger.Warningf("[%s-%s] driver closed for business", service, d.meta.Name)
+}
+
+// Close close all the channels for the driver and signal the child process to exit.
+func (d *BuiltinDriver) Close() {
+	if d.stream != nil {
+		close(d.stream)
+		d.stream = nil
+	}
+	if d.output != nil {
+		d.output.Close()
+		d.output = nil
+	}
+}
+
+// Wait wait for the driver process to exit.
+func (d *BuiltinDriver) Wait() {
+	_ = d.run.Wait()
+	d.run = nil
+}
diff --git a/internal/driver/driver.go b/internal/driver/driver.go
index 69e3174..25486de 100644
--- a/internal/driver/driver.go
+++ b/internal/driver/driver.go
@@ -9,9 +9,7 @@ package driver
 import (
 	"errors"
 	"fmt"
-	"io"
-	"os"
-	"os/exec"
+	"time"
 
 	"github.com/honeydipper/honeydipper/internal/daemon"
 	"github.com/honeydipper/honeydipper/pkg/dipper"
@@ -27,19 +25,31 @@ const DriverMessageBuffer = 10
 // Handler provides common functions for handling a driver.
 type Handler interface {
 	Acquire()
-	Prepare()
 	Meta() *Meta
+	Prepare(input chan<- *dipper.Message)
+	SendMessage(*dipper.Message)
+	Start(string)
+	Close()
+	Wait()
 }
 
-// replace the func variable with mock during testing.
-var execCommand = exec.Command
+// Runtime contains the runtime information of the running driver.
+type Runtime struct {
+	Data        interface{}
+	DynamicData interface{}
+	Feature     string
+	Handler     Handler
+	Stream      <-chan *dipper.Message
+	Service     string
+	State       int
+}
 
 // NewDriver creates a driver object to represent a child process.
-func NewDriver(data map[string]interface{}) Handler {
+func NewDriver(feature string, metaData map[string]interface{}, driverData interface{}, dynamicData interface{}) *Runtime {
 	var meta Meta
-	err := mapstructure.Decode(data, &meta)
+	err := mapstructure.Decode(metaData, &meta)
 	if err != nil {
-		panic(fmt.Errorf("malformat driver meta: %+v: %w", data, err))
+		panic(fmt.Errorf("malformat driver meta: %+v: %w", metaData, err))
 	}
 
 	if meta.Name == "" {
@@ -51,62 +61,32 @@ func NewDriver(data map[string]interface{}) Handler {
 	switch meta.Type {
 	case "builtin":
 		dh = NewBuiltinDriver(&meta)
+	case "null":
+		dh = NewNullDriver(&meta)
 	default:
 		panic(fmt.Errorf("%w: unsupported driver type: %s", ErrDriverError, meta.Type))
 	}
 
-	dh.Acquire()
-	dh.Prepare()
+	stream := make(chan *dipper.Message, DriverMessageBuffer)
 
-	if meta.Executable == "" {
-		panic(fmt.Errorf("%w: executable not defined for driver: %s", ErrDriverError, meta.Name))
+	runtime := &Runtime{
+		Data:        driverData,
+		DynamicData: dynamicData,
+		Feature:     feature,
+		Handler:     dh,
+		Stream:      stream,
 	}
 
-	return dh
-}
+	dh.Acquire()
+	dh.Prepare(stream)
 
-// Runtime contains the runtime information of the running driver.
-type Runtime struct {
-	Data        interface{}
-	DynamicData interface{}
-	Feature     string
-	Stream      chan dipper.Message
-	Input       io.ReadCloser
-	Output      io.WriteCloser
-	Service     string
-	Run         *exec.Cmd
-	State       int
-	Handler     Handler
+	return runtime
 }
 
 // Start the driver child process.  The "service" indicates which service this driver belongs to.
 func (runtime *Runtime) Start(service string) {
 	runtime.Service = service
-
-	m := runtime.Handler.Meta()
-
-	run := execCommand(m.Executable, append([]string{service}, m.Arguments...)...)
-	if input, err := run.StdoutPipe(); err != nil {
-		dipper.Logger.Panicf("[%s] Unable to link to driver stdout %v", service, err)
-	} else {
-		runtime.Input = input
-		runtime.Stream = make(chan dipper.Message, DriverMessageBuffer)
-		go runtime.fetchMessages()
-	}
-
-	if output, err := run.StdinPipe(); err != nil {
-		dipper.Logger.Panicf("[%s] Unable to link to driver stdin %v", service, err)
-	} else {
-		runtime.Output = output
-	}
-
-	run.Stderr = os.Stderr
-	run.ExtraFiles = []*os.File{os.Stdout} // giving child process stdout for logging
-	if err := run.Start(); err != nil {
-		dipper.Logger.Panicf("[%s] Failed to start driver %v", service, err)
-	}
-
-	runtime.Run = run
+	runtime.Handler.Start(service)
 	runtime.SendOptions()
 }
 
@@ -127,7 +107,7 @@ func (runtime *Runtime) SendOptions() {
 	})
 }
 
-// SendMessage sentds a dipper message to the driver child process.
+// SendMessage sends a dipper message to the driver child process.
 func (runtime *Runtime) SendMessage(msg *dipper.Message) {
 	if runtime.Feature != "emitter" {
 		if emitter, ok := daemon.Emitters[runtime.Service]; ok {
@@ -140,26 +120,18 @@ func (runtime *Runtime) SendMessage(msg *dipper.Message) {
 			})
 		}
 	}
-	dipper.SendMessage(runtime.Output, msg)
+	runtime.Handler.SendMessage(msg)
 }
 
-func (runtime *Runtime) fetchMessages() {
-	quit := false
-	daemon.Children.Add(1)
-	defer daemon.Children.Done()
-	for !quit && !daemon.ShuttingDown {
-		func() {
-			defer dipper.SafeExitOnError(
-				"failed to fetching messages from driver %s.%s",
-				runtime.Service,
-				runtime.Handler.Meta().Name,
-			)
-			defer dipper.CatchError(io.EOF, func() { quit = true })
-			for !quit && !daemon.ShuttingDown {
-				message := dipper.FetchRawMessage(runtime.Input)
-				runtime.Stream <- *message
-			}
-		}()
+// Ready waits until the driver is alive or report error.
+func (runtime *Runtime) Ready(d time.Duration) {
+	var elapsed time.Duration
+	for runtime.State < DriverAlive && elapsed < d {
+		time.Sleep(time.Second)
+		elapsed += time.Second
+	}
+
+	if runtime.State != DriverAlive {
+		panic(fmt.Errorf("%w: feature failed or loading timeout: %s", ErrDriverError, runtime.Feature))
 	}
-	dipper.Logger.Warningf("[%s-%s] driver closed for business", runtime.Service, runtime.Handler.Meta().Name)
 }
diff --git a/internal/driver/nulldriver.go b/internal/driver/nulldriver.go
new file mode 100644
index 0000000..ab49c70
--- /dev/null
+++ b/internal/driver/nulldriver.go
@@ -0,0 +1,82 @@
+// Copyright 2023 PayPal Inc.
+
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT License was not distributed with this file,
+// you can obtain one at https://mit-license.org/.
+
+package driver
+
+import (
+	"github.com/honeydipper/honeydipper/pkg/dipper"
+)
+
+// NullDriverHandler provides support for null driver.
+type NullDriverHandler struct {
+	AcquireFunc     func()
+	MetaFunc        func() *Meta
+	PrepareFunc     func(input chan<- *dipper.Message)
+	SendMessageFunc func(*dipper.Message)
+	StartFunc       func(string)
+	CloseFunc       func()
+	WaitFunc        func()
+}
+
+// Acquire does nothing unless overridden with AcquireFunc.
+func (h *NullDriverHandler) Acquire() {
+	if h.AcquireFunc != nil {
+		h.AcquireFunc()
+	}
+}
+
+// Meta does nothing unless overridden with MetaFunc.
+func (h *NullDriverHandler) Meta() *Meta {
+	if h.MetaFunc != nil {
+		return h.MetaFunc()
+	}
+
+	return nil
+}
+
+// Prepare does nothing unless overridden with PrepareFunc.
+func (h *NullDriverHandler) Prepare(stream chan<- *dipper.Message) {
+	if h.PrepareFunc != nil {
+		h.PrepareFunc(stream)
+	}
+}
+
+// Start does nothing unless overridden with StartFunc.
+func (h *NullDriverHandler) Start(service string) {
+	if h.StartFunc != nil {
+		h.StartFunc(service)
+	}
+}
+
+// SendMessage does nothing unless overridden with SendMessageFunc.
+func (h *NullDriverHandler) SendMessage(msg *dipper.Message) {
+	if h.SendMessageFunc != nil {
+		h.SendMessageFunc(msg)
+	}
+}
+
+// Close does nothing unless overridden with CloseFunc.
+func (h *NullDriverHandler) Close() {
+	if h.CloseFunc != nil {
+		h.CloseFunc()
+	}
+}
+
+// Wait does nothing unless overridden with WaitFunc.
+func (h *NullDriverHandler) Wait() {
+	if h.WaitFunc != nil {
+		h.WaitFunc()
+	}
+}
+
+// NewNullDriver creates a null driver handler.
+func NewNullDriver(meta *Meta) *NullDriverHandler {
+	return &NullDriverHandler{
+		MetaFunc: func() *Meta {
+			return meta
+		},
+	}
+}
diff --git a/internal/service/engine.go b/internal/service/engine.go
index 94a7406..fab4cc3 100644
--- a/internal/service/engine.go
+++ b/internal/service/engine.go
@@ -40,7 +40,7 @@ type WorkflowHelper struct {
 // SendMessage method sends workflow messages to eventbus channle.
 func (h *WorkflowHelper) SendMessage(msg *dipper.Message) {
 	worker := h.engine.getDriverRuntime(dipper.ChannelEventbus)
-	dipper.SendMessage(worker.Output, msg)
+	worker.SendMessage(msg)
 }
 
 // GetConfig method feed config from service to workflow engine.
diff --git a/internal/service/operator.go b/internal/service/operator.go
index 5a26a40..2bdb091 100644
--- a/internal/service/operator.go
+++ b/internal/service/operator.go
@@ -40,7 +40,7 @@ func handleEventbusCommand(msg *dipper.Message) []RoutedMessage {
 				newLabels["status"] = "error"
 				newLabels["reason"] = fmt.Sprintf("%+v", r)
 				eventbus := operator.getDriverRuntime(dipper.ChannelEventbus)
-				dipper.SendMessage(eventbus.Output, &dipper.Message{
+				eventbus.SendMessage(&dipper.Message{
 					Channel: dipper.ChannelEventbus,
 					Subject: dipper.EventbusReturn,
 					Labels:  newLabels,
diff --git a/internal/service/service.go b/internal/service/service.go
index fe39abb..21c80f0 100644
--- a/internal/service/service.go
+++ b/internal/service/service.go
@@ -9,7 +9,6 @@ package service
 import (
 	"errors"
 	"fmt"
-	"io"
 	"os"
 	"reflect"
 	"strconv"
@@ -129,23 +128,15 @@ func (s *Service) GetName() string {
 	return s.name
 }
 
-// GetStream returns the writer for communication with the specified feature.
-func (s *Service) GetStream(feature string) io.Writer {
-	if runtime := s.getDriverRuntime(feature); runtime != nil {
-		seconds := 0
-		for seconds < 9 && (runtime.State == driver.DriverLoading || runtime.State == driver.DriverReloading) {
-			dipper.Logger.Warningf("[%s] waiting for feature %s to load/reload ...", s.name, feature)
-			time.Sleep(time.Second)
-			seconds++
-		}
-
-		if runtime.State != driver.DriverAlive {
-			panic(fmt.Errorf("%w: feature failed or loading timeout: %s", ErrServiceError, feature))
-		}
-
-		return runtime.Output
+// GetReceiver returns the driver object that receives the rpc messages.
+func (s *Service) GetReceiver(feature string) interface{} {
+	receiver := s.getDriverRuntime(feature)
+	if receiver == nil {
+		panic(fmt.Errorf("%w: feature not loaded: %s", ErrServiceError, feature))
 	}
-	panic(fmt.Errorf("%w: feature not loaded: %s", ErrServiceError, feature))
+	receiver.Ready(DriverReadyTimeout * time.Second)
+
+	return receiver
 }
 
 func (s *Service) loadFeature(feature string) (affected bool, driverName string, rerr error) {
@@ -192,34 +183,29 @@ func (s *Service) loadFeature(feature string) (affected bool, driverName string,
 		dynamicData = s.dynamicFeatureData[feature]
 	}
 
-	var driverHandler driver.Handler
-	if driverMeta, ok := s.config.GetStagedDriverData(fmt.Sprintf("daemon.drivers.%s", driverName)); ok {
-		driverHandler = driver.NewDriver(driverMeta.(map[string]interface{}))
-	} else {
+	driverMeta, ok := s.config.GetStagedDriverData(fmt.Sprintf("daemon.drivers.%s", driverName))
+	if !ok {
 		panic("unable to get driver metadata")
 	}
 
-	dipper.Logger.Debugf("[%s] driver %s meta %v", s.name, driverName, driverHandler.Meta())
-	driverRuntime := driver.Runtime{
-		Feature:     feature,
-		Data:        driverData,
-		DynamicData: dynamicData,
-		State:       driver.DriverLoading,
-		Handler:     driverHandler,
-	}
+	driverRuntime := driver.NewDriver(feature, driverMeta.(map[string]interface{}), driverData, dynamicData)
+	dipper.Logger.Debugf("[%s] driver %s meta %v", s.name, driverName, driverRuntime.Handler.Meta())
+
+	driverMetaUnchanged := oldRuntime != nil && reflect.DeepEqual(*oldRuntime.Handler.Meta(), *driverRuntime.Handler.Meta())
+	driverRunning := oldRuntime != nil && oldRuntime.State != driver.DriverFailed
 
-	if oldRuntime != nil && oldRuntime.State != driver.DriverFailed && reflect.DeepEqual(*oldRuntime.Handler.Meta(), *driverHandler.Meta()) {
+	if driverRunning && driverMetaUnchanged {
 		if reflect.DeepEqual(oldRuntime.Data, driverRuntime.Data) && reflect.DeepEqual(oldRuntime.DynamicData, driverRuntime.DynamicData) {
 			dipper.Logger.Infof("[%s] driver not affected: %s", s.name, driverName)
 		} else {
 			// hot reload
 			affected = true
-			s.hotReload(&driverRuntime, oldRuntime)
+			s.hotReload(driverRuntime, oldRuntime)
 		}
 	} else {
 		// cold reload
 		affected = true
-		s.coldReload(&driverRuntime, oldRuntime)
+		s.coldReload(driverRuntime, oldRuntime)
 	}
 
 	return affected, driverName, nil
@@ -239,10 +225,9 @@ func (s *Service) coldReload(driverRuntime *driver.Runtime, oldRuntime *driver.R
 	go func(s *Service, runtime *driver.Runtime) {
 		defer dipper.SafeExitOnError("[%s] driver runtime %s crash", s.name, runtime.Handler.Meta().Name)
 		defer s.checkDeleteDriverRuntime(runtime.Feature, runtime)
-		defer close(runtime.Stream)
+		defer runtime.Handler.Close()
 
-		//nolint:errcheck
-		runtime.Run.Wait()
+		runtime.Handler.Wait()
 	}(s, driverRuntime)
 
 	if oldRuntime != nil {
@@ -255,7 +240,7 @@ func (s *Service) coldReload(driverRuntime *driver.Runtime, oldRuntime *driver.R
 			defer dipper.SafeExitOnError("[%s] runtime %s being replaced output is already closed", s.name, runtime.Handler.Meta().Name)
 			// allow 50 millisecond for the data to drain
 			time.Sleep(DriverGracefulTimeout * time.Millisecond)
-			runtime.Output.Close()
+			runtime.Handler.Close()
 		}(oldRuntime)
 	}
 }
@@ -338,7 +323,7 @@ func (s *Service) removeUnusedFeatures(featureList map[string]bool) {
 				defer dipper.SafeExitOnError("[%s] unused runtime %s output is already closed", s.name, runtime.Handler.Meta().Name)
 				// allow 50 millisecond for the data to drain
 				time.Sleep(DriverGracefulTimeout * time.Millisecond)
-				runtime.Output.Close()
+				runtime.Handler.Close()
 			}(runtime)
 		}
 	}
@@ -468,7 +453,7 @@ func (s *Service) serviceLoop() {
 			func() {
 				defer dipper.SafeExitOnError("[%s] service loop continue", s.name)
 				runtime := orderedRuntimes[chosen]
-				msg := value.Interface().(dipper.Message)
+				msg := value.Interface().(*dipper.Message)
 				if runtime.Feature != FeatureEmitter {
 					if emitter, ok := daemon.Emitters[s.name]; ok {
 						emitter.CounterIncr("honey.honeydipper.local.message", []string{
@@ -483,7 +468,7 @@ func (s *Service) serviceLoop() {
 
 				s.driverLock.Lock()
 				defer s.driverLock.Unlock()
-				go s.process(msg, runtime)
+				go s.process(*msg, runtime)
 			}()
 
 		case !ok && chosen < len(orderedRuntimes):
@@ -505,7 +490,7 @@ func (s *Service) serviceLoop() {
 	for fname, runtime := range s.driverRuntimes {
 		func() {
 			defer dipper.SafeExitOnError("[%s] driver runtime for feature %s already closed", s.name, fname)
-			runtime.Output.Close()
+			runtime.Handler.Close()
 		}()
 	}
 	dipper.Logger.Warningf("[%s] service closed for business", s.name)
@@ -636,7 +621,7 @@ func (s *Service) checkDeleteDriverRuntime(feature string, check *driver.Runtime
 func coldReloadDriverRuntime(d *driver.Runtime, m *dipper.Message) {
 	s := Services[d.Service]
 	s.checkDeleteDriverRuntime(d.Feature, d)
-	d.Output.Close()
+	d.Handler.Close()
 	dipper.Must(s.loadFeature(d.Feature))
 }
 
@@ -684,7 +669,7 @@ func handleRPCCall(from *driver.Runtime, m *dipper.Message) {
 	feature := m.Labels["feature"]
 	m.Labels["caller"] = from.Feature
 	s := Services[from.Service]
-	dipper.SendMessage(s.getDriverRuntime(feature).Output, m)
+	s.getDriverRuntime(feature).SendMessage(m)
 }
 
 func handleRPCReturn(from *driver.Runtime, m *dipper.Message) {
@@ -693,14 +678,14 @@ func handleRPCReturn(from *driver.Runtime, m *dipper.Message) {
 	if caller == "-" {
 		s.HandleReturn(m)
 	} else {
-		dipper.SendMessage(s.getDriverRuntime(caller).Output, m)
+		s.getDriverRuntime(caller).SendMessage(m)
 	}
 }
 
 func handleAPI(from *driver.Runtime, m *dipper.Message) {
 	s := Services[from.Service]
 	dipper.DeserializePayload(m)
-	resp := s.ResponseFactory.NewResponse(s, s.getDriverRuntime("eventbus").Output, m)
+	resp := s.ResponseFactory.NewResponse(s, s.GetReceiver("eventbus").(dipper.MessageReceiver), m)
 	if resp == nil {
 		dipper.Logger.Debugf("[%s] skipping handling API: %+v", s.name, m.Labels)
 
diff --git a/pkg/dipper/comm.go b/pkg/dipper/comm.go
index 69f8493..b226426 100644
--- a/pkg/dipper/comm.go
+++ b/pkg/dipper/comm.go
@@ -52,6 +52,23 @@ type Message struct {
 // MessageHandler : a type of functions that take a pointer to a message and handle it.
 type MessageHandler func(*Message)
 
+// MessageReceiver is a object that can receive and handle a dipper message.
+type MessageReceiver interface {
+	SendMessage(*Message)
+}
+
+// NullReceiver is a placeholder for MessageReceiver.
+type NullReceiver struct {
+	SendMessageFunc MessageHandler
+}
+
+// SendMessage does nothing unless overridden with SendMessageFunc.
+func (n *NullReceiver) SendMessage(msg *Message) {
+	if n.SendMessageFunc != nil {
+		n.SendMessageFunc(msg)
+	}
+}
+
 // SerializeContent : encode payload content into bytes.
 func SerializeContent(content interface{}) (ret []byte) {
 	var err error
diff --git a/pkg/dipper/driver.go b/pkg/dipper/driver.go
index b513dd6..b32f06e 100644
--- a/pkg/dipper/driver.go
+++ b/pkg/dipper/driver.go
@@ -198,9 +198,9 @@ func (d *Driver) GetLogger() *logging.Logger {
 	return Logger
 }
 
-// GetStream getting a output stream for a feature.
-func (d *Driver) GetStream(feature string) io.Writer {
-	return d.Out
+// GetReceiver getting a object that receive objects through SendMessage.
+func (d *Driver) GetReceiver(feature string) interface{} {
+	return d
 }
 
 // GetName returns the name of the driver.
diff --git a/pkg/dipper/mock_dipper/rpc.go b/pkg/dipper/mock_dipper/rpc.go
index c5d75d9..cce8baf 100644
--- a/pkg/dipper/mock_dipper/rpc.go
+++ b/pkg/dipper/mock_dipper/rpc.go
@@ -12,7 +12,6 @@
 package mock_dipper
 
 import (
-	io "io"
 	reflect "reflect"
 
 	gomock "github.com/golang/mock/gomock"
@@ -55,18 +54,18 @@ func (mr *MockRPCCallerStubMockRecorder) GetName() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockRPCCallerStub)(nil).GetName))
 }
 
-// GetStream mocks base method.
-func (m *MockRPCCallerStub) GetStream(feature string) io.Writer {
+// GetReceiver mocks base method.
+func (m *MockRPCCallerStub) GetReceiver(feature string) interface{} {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetStream", feature)
-	ret0, _ := ret[0].(io.Writer)
+	ret := m.ctrl.Call(m, "GetReceiver", feature)
+	ret0, _ := ret[0].(interface{})
 	return ret0
 }
 
-// GetStream indicates an expected call of GetStream.
-func (mr *MockRPCCallerStubMockRecorder) GetStream(feature interface{}) *gomock.Call {
+// GetReceiver indicates an expected call of GetReceiver.
+func (mr *MockRPCCallerStubMockRecorder) GetReceiver(feature interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStream", reflect.TypeOf((*MockRPCCallerStub)(nil).GetStream), feature)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReceiver", reflect.TypeOf((*MockRPCCallerStub)(nil).GetReceiver), feature)
 }
 
 // MockRPCCaller is a mock of RPCCaller interface.
diff --git a/pkg/dipper/rpc.go b/pkg/dipper/rpc.go
index 4d9652f..da50407 100644
--- a/pkg/dipper/rpc.go
+++ b/pkg/dipper/rpc.go
@@ -31,7 +31,7 @@ type RPCHandler func(string, string, []byte)
 // RPCCallerStub is an interface which every RPC caller should implement.
 type RPCCallerStub interface {
 	GetName() string
-	GetStream(feature string) io.Writer
+	GetReceiver(feature string) interface{}
 }
 
 // RPCCaller defines all method required for making rpc alls.
@@ -118,13 +118,13 @@ func (c *RPCCallerBase) CallRawNoWait(feature string, method string, params []by
 		rpcID = "skip"
 	}
 
-	out := c.Parent.GetStream(feature)
-	if out == nil {
+	receiver := c.Parent.GetReceiver(feature).(MessageReceiver)
+	if receiver == nil {
 		return fmt.Errorf("%w: feature not available: %s", ErrRPCError, feature)
 	}
 
 	// making the call by sending a message
-	SendMessage(out, &Message{
+	receiver.SendMessage(&Message{
 		Channel: c.Channel,
 		Subject: c.Subject,
 		Labels: map[string]string{


Test patch:
diff --git a/cmd/honeydipper/integration_test.go b/cmd/honeydipper/integration_test.go
index f345d5e..37220db 100644
--- a/cmd/honeydipper/integration_test.go
+++ b/cmd/honeydipper/integration_test.go
@@ -40,10 +40,17 @@ func TestIntegrationStart(t *testing.T) {
 	t.Run("checking services", intTestServices)
 	t.Run("checking processes", intTestProcesses)
 	t.Run("checking API calls", intTestMakingAPICall)
+	t.Run("checking config integrity", intTestConfig)
 	t.Run("checking crashed driver", intTestDriverCrash)
 	t.Run("checking draining drivers", intTestDrain)
 }
 
+func intTestConfig(t *testing.T) {
+	assert.Contains(t, cfg.DataSet.Workflows, "workflow1", "workflow1 should be loaded through glob file loading")
+	assert.Contains(t, cfg.DataSet.Workflows, "workflow2", "workflow2 should be loaded through glob file loading")
+	assert.NotContains(t, cfg.DataSet.Workflows, "not-included", "not-included should NOT be loaded through glob file loading")
+}
+
 func intTestDaemonStartup(t *testing.T) {
 	if dipper.Logger == nil {
 		logFile, err := os.Create("test.log")
@@ -65,7 +72,7 @@ func intTestDaemonStartup(t *testing.T) {
 		ref := strings.Split(string(currentBranch.Name()), "/")
 		workingBranch = ref[len(ref)-1]
 	}
-	cfg := config.Config{
+	cfg = config.Config{
 		Services: []string{"engine", "receiver", "operator", "api"},
 		InitRepo: config.RepoInfo{
 			Repo:   "../..",
@@ -200,7 +207,7 @@ func intTestDaemonShutdown(t *testing.T) {
 }
 
 func intTestDriverCrash(t *testing.T) {
-	assert.NotPanics(t, func() { _ = service.Services["operator"].GetStream("driver:gcloud-dataflow") }, "driver:gcloud-dataflow should be ready by now")
+	assert.NotPanics(t, func() { _ = service.Services["operator"].GetReceiver("driver:gcloud-dataflow") }, "driver:gcloud-dataflow should be ready by now")
 	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 	defer cancel()
 	var (
diff --git a/cmd/honeydipper/test_fixtures/bootstrap/glob/not-included.yaml b/cmd/honeydipper/test_fixtures/bootstrap/glob/not-included.yaml
new file mode 100644
index 0000000..771f2b6
--- /dev/null
+++ b/cmd/honeydipper/test_fixtures/bootstrap/glob/not-included.yaml
@@ -0,0 +1,3 @@
+---
+workflows:
+  not-included: {}
diff --git a/cmd/honeydipper/test_fixtures/bootstrap/glob/workflow1.yaml b/cmd/honeydipper/test_fixtures/bootstrap/glob/workflow1.yaml
new file mode 100644
index 0000000..783652f
--- /dev/null
+++ b/cmd/honeydipper/test_fixtures/bootstrap/glob/workflow1.yaml
@@ -0,0 +1,3 @@
+---
+workflows:
+  workflow1: {}
diff --git a/cmd/honeydipper/test_fixtures/bootstrap/glob/workflow2.yaml b/cmd/honeydipper/test_fixtures/bootstrap/glob/workflow2.yaml
new file mode 100644
index 0000000..a7bcfb2
--- /dev/null
+++ b/cmd/honeydipper/test_fixtures/bootstrap/glob/workflow2.yaml
@@ -0,0 +1,3 @@
+---
+workflows:
+  workflow2: {}
diff --git a/cmd/honeydipper/test_fixtures/bootstrap/init.yaml b/cmd/honeydipper/test_fixtures/bootstrap/init.yaml
index d06e733..74356a8 100644
--- a/cmd/honeydipper/test_fixtures/bootstrap/init.yaml
+++ b/cmd/honeydipper/test_fixtures/bootstrap/init.yaml
@@ -4,3 +4,4 @@ includes:
   - drivers.yaml
   - honey-queue.yaml
   - rules.yaml
+  - glob/work*.yaml
diff --git a/internal/api/response_test.go b/internal/api/response_test.go
index d436b4e..29b745f 100644
--- a/internal/api/response_test.go
+++ b/internal/api/response_test.go
@@ -11,7 +11,6 @@ package api
 
 import (
 	"fmt"
-	"io"
 	"testing"
 	"time"
 
@@ -36,20 +35,17 @@ func responseTest(t *testing.T, c *ResponseTestCase) {
 	defer ctrl.Finish()
 	mockRPCCaller := mock_dipper.NewMockRPCCaller(ctrl)
 
-	eventbusO, eventbusI := io.Pipe()
-	defer eventbusO.Close()
-	defer eventbusI.Close()
+	stream := make(chan *dipper.Message, 1)
+	mockReceiver := &dipper.NullReceiver{
+		SendMessageFunc: func(msg *dipper.Message) {
+			stream <- msg
+		},
+	}
+
 	waitForMsg := func(delay time.Duration) *dipper.Message {
 		var m *dipper.Message
-		msgAvailable := make(chan byte)
-		go func() {
-			defer recover()
-			defer close(msgAvailable)
-			m = dipper.FetchMessage(eventbusO)
-			m.Size = 0 // ignore Size property, only used for raw msg
-		}()
 		select {
-		case <-msgAvailable:
+		case m = <-stream:
 		case <-time.After(delay):
 		}
 
@@ -67,7 +63,7 @@ func responseTest(t *testing.T, c *ResponseTestCase) {
 	factory := NewResponseFactory()
 	factory.DefsByName = c.defsByName
 
-	resp := factory.NewResponse(mockRPCCaller, eventbusI, c.msg)
+	resp := factory.NewResponse(mockRPCCaller, mockReceiver, c.msg)
 	if c.noResponse {
 		assert.Nil(t, resp)
 	} else {
diff --git a/internal/driver/builtin_test.go b/internal/driver/builtin_test.go
index ce2bbf6..9f418fb 100644
--- a/internal/driver/builtin_test.go
+++ b/internal/driver/builtin_test.go
@@ -13,6 +13,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/honeydipper/honeydipper/pkg/dipper"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -113,7 +114,8 @@ func TestBuiltinPrepare(t *testing.T) {
 					}
 				}
 			}()
-			c[0].(*BuiltinDriver).Prepare()
+			stream := make(chan *dipper.Message, 1)
+			c[0].(*BuiltinDriver).Prepare(stream)
 		}(tc.([]interface{}))
 	}
 }
diff --git a/internal/driver/driver_test.go b/internal/driver/driver_test.go
index d7ceb95..933cb10 100644
--- a/internal/driver/driver_test.go
+++ b/internal/driver/driver_test.go
@@ -53,7 +53,7 @@ func TestDriverNewDriver(t *testing.T) {
 					}
 				}
 			}()
-			NewDriver(c[0].(map[string]interface{}))
+			NewDriver("testfeature", c[0].(map[string]interface{}), nil, nil)
 		}(tc.([]interface{}))
 	}
 }
diff --git a/internal/service/service_test.go b/internal/service/service_test.go
index 70dbe1b..90c8681 100644
--- a/internal/service/service_test.go
+++ b/internal/service/service_test.go
@@ -29,18 +29,15 @@ func TestServiceLoopCatchError(t *testing.T) {
 		dipper.GetLogger("test service", "DEBUG", f, f)
 	}
 
+	stream := make(chan *dipper.Message, 1)
 	svc := &Service{
 		name: "testsvc",
 		driverRuntimes: map[string]*driver.Runtime{
 			"d1": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "testdriver1",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
-					},
-				}),
+				Feature: "d1",
+				Handler: &driver.NullDriverHandler{},
+				State:   driver.DriverAlive,
+				Stream:  stream,
 			},
 		},
 		responders: map[string][]MessageResponder{
@@ -66,85 +63,77 @@ func TestServiceLoopCatchError(t *testing.T) {
 		},
 	}
 
-	svc.driverRuntimes["d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic in route")
 	}()
 	// injecting error in route
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "error0",
 	}
 	time.Sleep(30 * time.Millisecond)
 	// quiting faster by send an extra message
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "noerror",
 	}
 	daemon.ShutDown()
-	daemon.ShuttingDown = false
 
-	svc.driverRuntimes["d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
+	daemon.ShuttingDown = false
+	stream = make(chan *dipper.Message, 1)
+	svc.driverRuntimes["d1"].Stream = stream
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic in responder")
 	}()
 	// injecting error in responder
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "error1",
 	}
 	time.Sleep(30 * time.Millisecond)
 	// quiting faster by send an extra message
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "noerror",
 	}
 	daemon.ShutDown()
-	daemon.ShuttingDown = false
 
-	svc.driverRuntimes["d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
+	daemon.ShuttingDown = false
+	stream = make(chan *dipper.Message, 1)
+	svc.driverRuntimes["d1"].Stream = stream
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic in transformer")
 	}()
 	// injecting error in transformer
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "error2",
 	}
 	time.Sleep(30 * time.Millisecond)
 	// quiting faster by send an extra message
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "noerror",
 	}
 	daemon.ShutDown()
-	daemon.ShuttingDown = false
 
-	svc.driverRuntimes["d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
+	daemon.ShuttingDown = false
+	stream = make(chan *dipper.Message, 1)
+	svc.driverRuntimes["d1"].Stream = stream
 	// injecting error in process
 	svc.driverRuntimes["d1"].Handler = nil
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic in process itself")
 	}()
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "error3",
 	}
 	// recover the service object to avoid crash during quiting
-	svc.driverRuntimes["d1"].Handler = driver.NewDriver(map[string]interface{}{
-		"name": "testdriver1",
-		"type": "builtin",
-		"handlerData": map[string]interface{}{
-			"shortName": "testdriver1",
-		},
-	})
+	svc.driverRuntimes["d1"].Handler = &driver.NullDriverHandler{}
 	time.Sleep(30 * time.Millisecond)
 	// quiting faster by send an extra message
-	svc.driverRuntimes["d1"].Stream <- dipper.Message{
+	stream <- &dipper.Message{
 		Channel: "test",
 		Subject: "noerror",
 	}
@@ -159,29 +148,27 @@ func TestServiceRemoveEmitter(t *testing.T) {
 		dipper.GetLogger("test service", "DEBUG", f, f)
 	}
 
+	stream := make(chan *dipper.Message, 1)
 	svc := &Service{
 		name: "testsvc",
 		driverRuntimes: map[string]*driver.Runtime{
 			"driver:d1": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "d1",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
-					},
+				Feature: "driver:d1",
+				Handler: driver.NewNullDriver(&driver.Meta{
+					Name: "d1",
+					Type: "null",
 				}),
+				State:  driver.DriverAlive,
+				Stream: stream,
 			},
 			"emitter": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "test-emitter",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
-					},
-				}),
 				Feature: "emitter",
+				Handler: driver.NewNullDriver(&driver.Meta{
+					Name: "test-emitter",
+					Type: "null",
+				}),
+				State:  driver.DriverAlive,
+				Stream: make(chan *dipper.Message),
 			},
 		},
 		Route: func(m *dipper.Message) []RoutedMessage {
@@ -191,10 +178,6 @@ func TestServiceRemoveEmitter(t *testing.T) {
 	daemon.Emitters["testsvc"] = svc
 
 	daemon.ShuttingDown = false
-	svc.driverRuntimes["driver:d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["driver:d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
-	svc.driverRuntimes["emitter"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["emitter"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, 0o777)
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic if emitter is removed")
 	}()
@@ -206,7 +189,7 @@ func TestServiceRemoveEmitter(t *testing.T) {
 		assert.NotPanics(t, func() {
 			for i := 0; i < 50; i++ {
 				select {
-				case svc.driverRuntimes["driver:d1"].Stream <- dipper.Message{
+				case stream <- &dipper.Message{
 					Channel: "test",
 					Subject: "noerror",
 				}:
@@ -234,7 +217,7 @@ func TestServiceRemoveEmitter(t *testing.T) {
 					"drivers": map[string]interface{}{
 						"d1": map[string]interface{}{
 							"name": "d1",
-							"type": "builtin",
+							"type": "null",
 						},
 					},
 				},
@@ -258,29 +241,28 @@ func TestServiceEmitterCrashing(t *testing.T) {
 		dipper.GetLogger("test service", "DEBUG", f, f)
 	}
 
+	stream := make(chan *dipper.Message, 1)
+	emitterStream := make(chan *dipper.Message, 1)
 	svc := &Service{
 		name: "testsvc",
 		driverRuntimes: map[string]*driver.Runtime{
 			"driver:d1": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "d1",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
-					},
-				}),
+				Feature: "d1",
+				Handler: &driver.NullDriverHandler{},
+				State:   driver.DriverAlive,
+				Stream:  stream,
 			},
 			"emitter": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "test-emitter",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
+				Feature: "test-emitter",
+				Handler: &driver.NullDriverHandler{
+					SendMessageFunc: func(*dipper.Message) {
+						if emitterStream == nil {
+							panic("emitter crashed")
+						}
 					},
-				}),
-				Feature: "emitter",
+				},
+				State:  driver.DriverAlive,
+				Stream: emitterStream,
 			},
 		},
 		Route: func(m *dipper.Message) []RoutedMessage {
@@ -290,10 +272,6 @@ func TestServiceEmitterCrashing(t *testing.T) {
 	daemon.Emitters["testsvc"] = svc
 
 	daemon.ShuttingDown = false
-	svc.driverRuntimes["driver:d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["driver:d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
-	svc.driverRuntimes["emitter"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["emitter"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, 0o777)
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic if emitter crashes")
 	}()
@@ -305,7 +283,7 @@ func TestServiceEmitterCrashing(t *testing.T) {
 		assert.NotPanics(t, func() {
 			for i := 0; i < 50; i++ {
 				select {
-				case svc.driverRuntimes["driver:d1"].Stream <- dipper.Message{
+				case stream <- &dipper.Message{
 					Channel: "test",
 					Subject: "noerror",
 				}:
@@ -321,8 +299,8 @@ func TestServiceEmitterCrashing(t *testing.T) {
 	// mark it as failed to avoid restarting the driver
 	svc.driverRuntimes["emitter"].State = driver.DriverFailed
 	// crash emitter
-	svc.driverRuntimes["emitter"].Output.Close()
-	close(svc.driverRuntimes["emitter"].Stream)
+	close(emitterStream)
+	emitterStream = nil
 	time.Sleep(100 * time.Millisecond)
 
 	daemon.ShutDown()
@@ -335,29 +313,31 @@ func TestServiceReplaceEmitter(t *testing.T) {
 		dipper.GetLogger("test service", "DEBUG", f, f)
 	}
 
+	stream := make(chan *dipper.Message, 1)
+	emitterStream := make(chan *dipper.Message, 1)
 	svc := &Service{
 		name: "testsvc",
 		driverRuntimes: map[string]*driver.Runtime{
 			"driver:d1": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "d1",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
-					},
+				Feature: "driver:d1",
+				Handler: driver.NewNullDriver(&driver.Meta{
+					Name: "d1",
+					Type: "null",
 				}),
+				State:  driver.DriverAlive,
+				Stream: stream,
 			},
 			"emitter": {
-				State: driver.DriverAlive,
-				Handler: driver.NewDriver(map[string]interface{}{
-					"name": "test-emitter",
-					"type": "builtin",
-					"handlerData": map[string]interface{}{
-						"shortName": "testdriver1",
+				Feature: "test-emitter",
+				Handler: &driver.NullDriverHandler{
+					SendMessageFunc: func(*dipper.Message) {
+						if emitterStream == nil {
+							panic("emitter crashed")
+						}
 					},
-				}),
-				Feature: "emitter",
+				},
+				State:  driver.DriverAlive,
+				Stream: emitterStream,
 			},
 		},
 		Route: func(m *dipper.Message) []RoutedMessage {
@@ -367,10 +347,6 @@ func TestServiceReplaceEmitter(t *testing.T) {
 	daemon.Emitters["testsvc"] = svc
 
 	daemon.ShuttingDown = false
-	svc.driverRuntimes["driver:d1"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["driver:d1"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND, 0o777)
-	svc.driverRuntimes["emitter"].Stream = make(chan dipper.Message, 1)
-	svc.driverRuntimes["emitter"].Output, _ = os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, 0o777)
 	go func() {
 		assert.NotPanics(t, svc.serviceLoop, "service loop should recover panic if emitter is changed")
 	}()
@@ -382,7 +358,7 @@ func TestServiceReplaceEmitter(t *testing.T) {
 		assert.NotPanics(t, func() {
 			for i := 0; i < 50; i++ {
 				select {
-				case svc.driverRuntimes["driver:d1"].Stream <- dipper.Message{
+				case stream <- &dipper.Message{
 					Channel: "test",
 					Subject: "noerror",
 				}:
@@ -416,9 +392,11 @@ func TestServiceReplaceEmitter(t *testing.T) {
 					"drivers": map[string]interface{}{
 						"d1": map[string]interface{}{
 							"name": "d1",
+							"type": "null",
 						},
 						"emitter2": map[string]interface{}{
 							"name": "emitter2",
+							"type": "null",
 						},
 					},
 				},
diff --git a/pkg/dipper/encryption_test.go b/pkg/dipper/encryption_test.go
index c982153..cdda32b 100644
--- a/pkg/dipper/encryption_test.go
+++ b/pkg/dipper/encryption_test.go
@@ -10,7 +10,6 @@
 package dipper
 
 import (
-	"io"
 	"testing"
 
 	"github.com/ghodss/yaml"
@@ -19,32 +18,26 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func wrapDecryptAll(t *testing.T, doc string, expect func(io.Reader, *RPCCallerBase)) map[string]interface{} {
+func wrapDecryptAll(t *testing.T, doc string, expect []MessageReceiver, c *RPCCallerBase) map[string]interface{} {
 	var data map[string]interface{}
 	assert.NoError(t, yaml.Unmarshal([]byte(doc), &data))
 
-	O, I := io.Pipe()
-
+	curr := 0
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 	mockStub := mock_dipper.NewMockRPCCallerStub(ctrl)
-	c := &RPCCallerBase{}
 	mockStub.EXPECT().GetName().AnyTimes().Return("mockCaller")
-	mockStub.EXPECT().GetStream(gomock.Any()).AnyTimes().Return(I)
-	c.Init(mockStub, "rpc", "call")
+	mockStub.EXPECT().GetReceiver(gomock.AssignableToTypeOf("")).AnyTimes().DoAndReturn(func(x string) MessageReceiver {
+		ret := expect[curr]
+		curr++
 
-	done := make(chan struct{})
-	go func() {
-		assert.NotPanics(t, func() { expect(O, c) }, "should not panic when sending and receiving messages")
-		assert.PanicsWithError(t, "invalid message envelope: io: read/write on closed pipe", func() { FetchRawMessage(O) }, "should contain no more messages")
-		close(done)
-	}()
+		return ret
+	})
 
-	DecryptAll(c, data)
+	*c = RPCCallerBase{}
+	c.Init(mockStub, "rpc", "call")
 
-	O.Close()
-	I.Close()
-	<-done
+	DecryptAll(c, data)
 
 	return data
 }
@@ -56,41 +49,46 @@ data:
   item2: ENC[noexist,YWFiYmNjZGQ=]
 `
 
-	expect := func(O io.Reader, c *RPCCallerBase) {
-		assert.Equalf(t,
-			Message{
-				Channel: "rpc",
-				Subject: "call",
-				IsRaw:   true,
-				Size:    8,
-				Payload: []byte("aabbccdd"),
-				Labels: map[string]string{
-					"caller":  "-",
-					"feature": "driver:noexist",
-					"method":  "decrypt",
-					"rpcID":   "0",
-				},
-			},
-			*FetchRawMessage(O),
-			"should make call to decrypt item2",
-		)
-		c.HandleReturn(&Message{
-			Channel: "rpc",
-			Subject: "return",
-			IsRaw:   true,
-			Size:    18,
-			Payload: []byte("decrypted aabbccdd"),
-			Labels: map[string]string{
-				"caller":  "-",
-				"feature": "driver:noexist",
-				"method":  "decrypt",
-				"rpcID":   "0",
-				"status":  "success",
+	c := &RPCCallerBase{}
+
+	expect := []MessageReceiver{
+		&NullReceiver{
+			SendMessageFunc: func(msg *Message) {
+				assert.Equalf(t,
+					Message{
+						Channel: "rpc",
+						Subject: "call",
+						IsRaw:   true,
+						Payload: []byte("aabbccdd"),
+						Labels: map[string]string{
+							"caller":  "-",
+							"feature": "driver:noexist",
+							"method":  "decrypt",
+							"rpcID":   "0",
+						},
+					},
+					*msg,
+					"should make call to decrypt item2",
+				)
+				c.HandleReturn(&Message{
+					Channel: "rpc",
+					Subject: "return",
+					IsRaw:   true,
+					Size:    18,
+					Payload: []byte("decrypted aabbccdd"),
+					Labels: map[string]string{
+						"caller":  "-",
+						"feature": "driver:noexist",
+						"method":  "decrypt",
+						"rpcID":   "0",
+						"status":  "success",
+					},
+				})
 			},
-		})
+		},
 	}
 
-	data := wrapDecryptAll(t, doc, expect)
+	data := wrapDecryptAll(t, doc, expect, c)
 	assert.Equal(t, "decrypted aabbccdd", MustGetMapDataStr(data, "data.item2"), "data.item2 should container decrypted data")
 	assert.Equal(t, "not encrypted", MustGetMapDataStr(data, "data.item1"), "data.item1 should remain unchanged")
 }
@@ -103,10 +101,17 @@ data:
     item4: ENC[deferred,driver1,YWFiYmNjZGQ=]
 `
 
-	expect := func(O io.Reader, c *RPCCallerBase) {
+	c := &RPCCallerBase{}
+
+	expect := []MessageReceiver{
+		&NullReceiver{
+			SendMessageFunc: func(msg *Message) {
+				assert.Fail(t, "should not make calls for delayed decryption.")
+			},
+		},
 	}
 
-	data := wrapDecryptAll(t, doc, expect)
+	data := wrapDecryptAll(t, doc, expect, c)
 	assert.Equal(t, "ENC[driver1,YWFiYmNjZGQ=]", MustGetMapDataStr(data, "data.item3.item4"), "item4 should be stripped off one deferred flag")
 	assert.Equal(t, "not encrypted", MustGetMapDataStr(data, "data.item1"), "data.item1 should remain unchanged")
 }
@@ -118,41 +123,46 @@ data:
   item2: LOOKUP[kvstore,foo]
 `
 
-	expect := func(O io.Reader, c *RPCCallerBase) {
-		assert.Equalf(t,
-			Message{
-				Channel: "rpc",
-				Subject: "call",
-				IsRaw:   true,
-				Size:    3,
-				Payload: []byte("foo"),
-				Labels: map[string]string{
-					"caller":  "-",
-					"feature": "driver:kvstore",
-					"method":  "lookup",
-					"rpcID":   "0",
-				},
-			},
-			*FetchRawMessage(O),
-			"should make call to lookup for item2",
-		)
-		c.HandleReturn(&Message{
-			Channel: "rpc",
-			Subject: "return",
-			IsRaw:   true,
-			Size:    3,
-			Payload: []byte("bar"),
-			Labels: map[string]string{
-				"caller":  "-",
-				"feature": "driver:kvstore",
-				"method":  "lookup",
-				"rpcID":   "0",
-				"status":  "success",
+	c := &RPCCallerBase{}
+
+	expect := []MessageReceiver{
+		&NullReceiver{
+			SendMessageFunc: func(msg *Message) {
+				assert.Equalf(t,
+					Message{
+						Channel: "rpc",
+						Subject: "call",
+						IsRaw:   true,
+						Payload: []byte("foo"),
+						Labels: map[string]string{
+							"caller":  "-",
+							"feature": "driver:kvstore",
+							"method":  "lookup",
+							"rpcID":   "0",
+						},
+					},
+					*msg,
+					"should make call to lookup for item2",
+				)
+				c.HandleReturn(&Message{
+					Channel: "rpc",
+					Subject: "return",
+					IsRaw:   true,
+					Size:    3,
+					Payload: []byte("bar"),
+					Labels: map[string]string{
+						"caller":  "-",
+						"feature": "driver:kvstore",
+						"method":  "lookup",
+						"rpcID":   "0",
+						"status":  "success",
+					},
+				})
 			},
-		})
+		},
 	}
 
-	data := wrapDecryptAll(t, doc, expect)
+	data := wrapDecryptAll(t, doc, expect, c)
 	assert.Equal(t, "bar", MustGetMapDataStr(data, "data.item2"), "data.item2 should container decrypted data")
 	assert.Equal(t, "not encrypted", MustGetMapDataStr(data, "data.item1"), "data.item1 should remain unchanged")
 }
diff --git a/pkg/dipper/rpc_test.go b/pkg/dipper/rpc_test.go
index bab3f1f..957c11c 100644
--- a/pkg/dipper/rpc_test.go
+++ b/pkg/dipper/rpc_test.go
@@ -10,9 +10,6 @@
 package dipper
 
 import (
-	"bytes"
-	"fmt"
-	"io"
 	"testing"
 	"time"
 
@@ -25,72 +22,48 @@ func TestRPCCallRaw(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
-	mockStub := mock_dipper.NewMockRPCCallerStub(ctrl)
-
-	var b bytes.Buffer
+	curr := 0
 	c := RPCCallerBase{}
+	var m *Message
+
+	expect := []MessageReceiver{
+		&NullReceiver{
+			SendMessageFunc: func(msg *Message) {
+				m = msg
+			},
+		},
+	}
 
+	mockStub := mock_dipper.NewMockRPCCallerStub(ctrl)
 	mockStub.EXPECT().GetName().AnyTimes().Return("mockCaller")
-	mockStub.EXPECT().GetStream(gomock.Any()).Return(&b)
+	mockStub.EXPECT().GetReceiver(gomock.AssignableToTypeOf("")).AnyTimes().DoAndReturn(func(x string) MessageReceiver {
+		ret := expect[curr]
+		curr++
+
+		return ret
+	})
 	c.Init(mockStub, "rpc", "call")
 	assert.NotPanicsf(t, func() { c.CallRaw("target", "testmethod", []byte("hello world")) }, "CallRaw should not panic")
 	time.Sleep(time.Second / 10)
-	var channel, subject string
-	var size, numlabels int
-	fmt.Fscanln(&b, &channel, &subject, &numlabels, &size)
-	assert.Equal(t, "rpc", channel, "rpc call sends message through rpc channel")
-	assert.Equal(t, "call", subject, "rpc uses callee and method and prefix for subject")
-	assert.Equal(t, 11, size, "rpc call raw sends the bytes as payload")
-	assert.Equal(t, 4, numlabels, "rpc call use labels to specify feature and method")
-	var lname string
-	var lval []byte
-	var vl int
-	labels := map[string]string{}
-	fmt.Fscanln(&b, &lname, &vl)
-	if vl > 0 {
-		lval = make([]byte, vl)
-		Must(io.ReadFull(&b, lval))
-		labels[lname] = string(lval)
-	} else {
-		labels[lname] = ""
-	}
-	fmt.Fscanln(&b, &lname, &vl)
-	if vl > 0 {
-		lval = make([]byte, vl)
-		Must(io.ReadFull(&b, lval))
-		labels[lname] = string(lval)
-	} else {
-		labels[lname] = ""
-	}
-	fmt.Fscanln(&b, &lname, &vl)
-	if vl > 0 {
-		lval = make([]byte, vl)
-		Must(io.ReadFull(&b, lval))
-		labels[lname] = string(lval)
-	} else {
-		labels[lname] = ""
-	}
-	fmt.Fscanln(&b, &lname, &vl)
-	if vl > 0 {
-		lval = make([]byte, vl)
-		Must(io.ReadFull(&b, lval))
-		labels[lname] = string(lval)
-	} else {
-		labels[lname] = ""
-	}
-	lv, ok := labels["caller"]
+
+	assert.Equal(t, "rpc", m.Channel, "rpc call sends message through rpc channel")
+	assert.Equal(t, "call", m.Subject, "rpc uses callee and method and prefix for subject")
+	assert.Equal(t, 4, len(m.Labels), "rpc call use labels to specify feature and method")
+
+	lv, ok := m.Labels["caller"]
 	assert.True(t, ok, "rpc caller present")
 	assert.Equal(t, "-", lv, "rpc caller should be -")
-	lv, ok = labels["rpcID"]
+	lv, ok = m.Labels["rpcID"]
 	assert.True(t, ok, "rpc rpcID present")
 	assert.Equal(t, "0", lv, "rpcID should be 0")
-	lv, ok = labels["feature"]
+	lv, ok = m.Labels["feature"]
 	assert.True(t, ok, "rpc feature present")
 	assert.Equal(t, "target", lv, "feature should be target")
-	lv, ok = labels["method"]
+	lv, ok = m.Labels["method"]
 	assert.True(t, ok, "rpc method present")
+
 	assert.Equal(t, "testmethod", lv, "method should be testmethod")
-	received, err := io.ReadAll(&b)
-	assert.Nil(t, err, "rpc call payload should be readable")
+	received, ok := m.Payload.([]byte)
+	assert.True(t, ok, "rpc call payload should be byte array")
 	assert.Equal(t, "hello world", string(received), "rpc should be unchanged")
 }