Skip to content

Commit 22c22c6

Browse files
authored
feat: Reduce payload size for device events (#1012)
The proposal introduces a new global environment variable, EDGEX_OPTIMIZE_EVENT_PAYLOAD, for all EdgeX services. When EDGEX_OPTIMIZE_EVENT_PAYLOAD is set to true, the event payload will be optimized. Signed-off-by: bruce <[email protected]>
1 parent beaeb51 commit 22c22c6

File tree

4 files changed

+161
-25
lines changed

4 files changed

+161
-25
lines changed

common/constants.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,9 @@ const (
295295

296296
// Constants for Edgex Environment variable
297297
const (
298-
EnvEncodeAllEvents = "EDGEX_ENCODE_ALL_EVENTS_CBOR"
299-
EnvMessageCborEncode = "EDGEX_MSG_CBOR_ENCODE"
298+
EnvEncodeAllEvents = "EDGEX_ENCODE_ALL_EVENTS_CBOR"
299+
EnvMessageCborEncode = "EDGEX_MSG_CBOR_ENCODE"
300+
EnvOptimizeEventPayload = "EDGEX_OPTIMIZE_EVENT_PAYLOAD"
300301
)
301302

302303
// Miscellaneous constants

dtos/event.go

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
//
2-
// Copyright (C) 2020-2023 IOTech Ltd
2+
// Copyright (C) 2020-2025 IOTech Ltd
33
//
44
// SPDX-License-Identifier: Apache-2.0
55

66
package dtos
77

88
import (
9+
"encoding/json"
910
"encoding/xml"
11+
"os"
1012
"time"
1113

12-
"github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common"
13-
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
14-
14+
"github.com/fxamacker/cbor/v2"
1515
"github.com/google/uuid"
16+
17+
"github.com/edgexfoundry/go-mod-core-contracts/v4/common"
18+
dtoCommon "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common"
19+
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
1620
)
1721

1822
type Event struct {
19-
common.Versionable `json:",inline"`
20-
Id string `json:"id" validate:"required,uuid"`
21-
DeviceName string `json:"deviceName" validate:"required,edgex-dto-none-empty-string"`
22-
ProfileName string `json:"profileName" validate:"required,edgex-dto-none-empty-string"`
23-
SourceName string `json:"sourceName" validate:"required,edgex-dto-none-empty-string"`
24-
Origin int64 `json:"origin" validate:"required"`
25-
Readings []BaseReading `json:"readings" validate:"gt=0,dive,required"`
26-
Tags Tags `json:"tags,omitempty"`
23+
dtoCommon.Versionable `json:",inline"`
24+
Id string `json:"id" validate:"required,uuid"`
25+
DeviceName string `json:"deviceName" validate:"required,edgex-dto-none-empty-string"`
26+
ProfileName string `json:"profileName" validate:"required,edgex-dto-none-empty-string"`
27+
SourceName string `json:"sourceName" validate:"required,edgex-dto-none-empty-string"`
28+
Origin int64 `json:"origin" validate:"required"`
29+
Readings []BaseReading `json:"readings" validate:"gt=0,dive,required"`
30+
Tags Tags `json:"tags,omitempty"`
2731
}
2832

2933
// NewEvent creates and returns an initialized Event with no Readings
3034
func NewEvent(profileName, deviceName, sourceName string) Event {
3135
return Event{
32-
Versionable: common.NewVersionable(),
36+
Versionable: dtoCommon.NewVersionable(),
3337
Id: uuid.NewString(),
3438
DeviceName: deviceName,
3539
ProfileName: profileName,
@@ -51,7 +55,7 @@ func FromEventModelToDTO(event models.Event) Event {
5155
}
5256

5357
return Event{
54-
Versionable: common.NewVersionable(),
58+
Versionable: dtoCommon.NewVersionable(),
5559
Id: event.Id,
5660
DeviceName: event.DeviceName,
5761
ProfileName: event.ProfileName,
@@ -96,3 +100,101 @@ func (e *Event) ToXML() (string, error) {
96100

97101
return string(eventXml), nil
98102
}
103+
104+
func (e Event) MarshalJSON() ([]byte, error) {
105+
return e.marshal(json.Marshal)
106+
}
107+
108+
func (e Event) MarshalCBOR() ([]byte, error) {
109+
return e.marshal(cbor.Marshal)
110+
}
111+
112+
func (e Event) marshal(marshal func(any) ([]byte, error)) ([]byte, error) {
113+
var aux struct {
114+
dtoCommon.Versionable `json:",inline"`
115+
Id string `json:"id" validate:"required,uuid"`
116+
DeviceName string `json:"deviceName"`
117+
ProfileName string `json:"profileName"`
118+
SourceName string `json:"sourceName"`
119+
Origin int64 `json:"origin"`
120+
Readings []BaseReading `json:"readings"`
121+
Tags Tags `json:"tags,omitempty"`
122+
}
123+
aux.Versionable = e.Versionable
124+
aux.Id = e.Id
125+
aux.DeviceName = e.DeviceName
126+
aux.ProfileName = e.ProfileName
127+
aux.SourceName = e.SourceName
128+
aux.Origin = e.Origin
129+
aux.Tags = e.Tags
130+
if len(e.Readings) > 0 {
131+
aux.Readings = make([]BaseReading, len(e.Readings))
132+
}
133+
134+
if os.Getenv(common.EnvOptimizeEventPayload) == common.ValueTrue {
135+
for i, reading := range e.Readings {
136+
reading.Id = ""
137+
reading.DeviceName = ""
138+
reading.ProfileName = ""
139+
if e.Origin == reading.Origin {
140+
reading.Origin = 0
141+
}
142+
if len(e.Readings) == 1 && e.SourceName == reading.ResourceName {
143+
reading.ResourceName = ""
144+
}
145+
aux.Readings[i] = reading
146+
}
147+
} else {
148+
copy(aux.Readings, e.Readings)
149+
}
150+
151+
return marshal(aux)
152+
}
153+
154+
func (e *Event) UnmarshalJSON(b []byte) error {
155+
return e.unmarshal(b, json.Unmarshal)
156+
}
157+
158+
func (e *Event) UnmarshalCBOR(b []byte) error {
159+
return e.unmarshal(b, cbor.Unmarshal)
160+
}
161+
162+
func (e *Event) unmarshal(data []byte, unmarshal func([]byte, any) error) error {
163+
var aux struct {
164+
dtoCommon.Versionable
165+
Id string
166+
DeviceName string
167+
ProfileName string
168+
SourceName string
169+
Origin int64
170+
Readings []BaseReading
171+
Tags Tags
172+
}
173+
if err := unmarshal(data, &aux); err != nil {
174+
return err
175+
}
176+
177+
e.Versionable = aux.Versionable
178+
e.Id = aux.Id
179+
e.DeviceName = aux.DeviceName
180+
e.ProfileName = aux.ProfileName
181+
e.SourceName = aux.SourceName
182+
e.Origin = aux.Origin
183+
e.Readings = aux.Readings
184+
e.Tags = aux.Tags
185+
186+
if os.Getenv(common.EnvOptimizeEventPayload) == common.ValueTrue {
187+
// recover the reduced fields
188+
for i, reading := range e.Readings {
189+
e.Readings[i].DeviceName = e.DeviceName
190+
e.Readings[i].ProfileName = e.ProfileName
191+
if reading.Origin == 0 {
192+
e.Readings[i].Origin = e.Origin
193+
}
194+
if len(e.Readings) == 1 && len(reading.ResourceName) == 0 {
195+
e.Readings[i].ResourceName = e.SourceName
196+
}
197+
}
198+
}
199+
return nil
200+
}

dtos/event_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright (C) 2020-2021 IOTech Ltd
2+
// Copyright (C) 2020-2025 IOTech Ltd
33
//
44
// SPDX-License-Identifier: Apache-2.0
55

@@ -8,6 +8,8 @@ package dtos
88
import (
99
"encoding/json"
1010
"fmt"
11+
"math"
12+
"os"
1113
"testing"
1214

1315
"github.com/fxamacker/cbor/v2"
@@ -287,3 +289,34 @@ func TestEvent_MarshalNullReading(t *testing.T) {
287289
})
288290
}
289291
}
292+
293+
func TestEvent_MarshalOptimizedEventPayload(t *testing.T) {
294+
testEvent := NewEvent(TestDeviceProfileName, TestDeviceName, TestSourceName)
295+
testEvent.Id = TestUUID
296+
testEvent.Origin = TestTimestamp
297+
err := testEvent.AddSimpleReading(TestReadingName, common.ValueTypeUint8, uint8(math.MaxUint8))
298+
require.NoError(t, err)
299+
testEvent.Readings[0].Id = TestUUID
300+
testEvent.Readings[0].Origin = TestTimestamp
301+
302+
err = os.Setenv(common.EnvOptimizeEventPayload, common.ValueTrue)
303+
require.NoError(t, err)
304+
305+
// Marshal reduce the reading fields
306+
data, err := json.Marshal(testEvent)
307+
require.NoError(t, err)
308+
assert.JSONEq(t,
309+
fmt.Sprintf(
310+
`{"apiVersion":"v3", "id":"%s",
311+
"deviceName":"TestDevice","profileName":"TestDeviceProfileName","sourceName":"TestSourceName","origin":%d,
312+
"readings":[{"resourceName":"TestDeviceResource","valueType":"Uint8","value":"255"}]}`, TestUUID, TestTimestamp),
313+
string(data))
314+
315+
// Unmarshal recover the reading fields
316+
var res Event
317+
err = json.Unmarshal(data, &res)
318+
require.NoError(t, err)
319+
320+
testEvent.Readings[0].Id = "" // the id field will be omitted ,and it isn’t used to store in the database
321+
assert.Equal(t, testEvent, res)
322+
}

dtos/reading.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -538,14 +538,14 @@ func (b BaseReading) MarshalCBOR() ([]byte, error) {
538538

539539
func (b BaseReading) marshal(marshal func(any) ([]byte, error)) ([]byte, error) {
540540
type reading struct {
541-
Id string `json:"id,omitempty"`
542-
Origin int64 `json:"origin"`
543-
DeviceName string `json:"deviceName"`
544-
ResourceName string `json:"resourceName"`
545-
ProfileName string `json:"profileName"`
546-
ValueType string `json:"valueType"`
547-
Units string `json:"units,omitempty"`
548-
Tags Tags `json:"tags,omitempty"`
541+
Id string `json:"id,omitempty" cbor:"id,omitempty"`
542+
Origin int64 `json:"origin,omitempty" cbor:"origin,omitempty"`
543+
DeviceName string `json:"deviceName,omitempty" cbor:"deviceName,omitempty"`
544+
ResourceName string `json:"resourceName,omitempty" cbor:"resourceName,omitempty"`
545+
ProfileName string `json:"profileName,omitempty" cbor:"profileName,omitempty"`
546+
ValueType string `json:"valueType" cbor:"valueType"`
547+
Units string `json:"units,omitempty" cbor:"units,omitempty"`
548+
Tags Tags `json:"tags,omitempty" cbor:"tags,omitempty"`
549549
}
550550
if b.isNull {
551551
return marshal(&struct {

0 commit comments

Comments
 (0)