We have two physical AWS S3s called in the code s3Legacy
and s3Cache
. They store the same data but with different S3 key structures.
The fists solution shared below uses objects, the second does not.
Is the solution using objects an excessive abstraction or there are benefits to it?
(There is no need to comment on the use of the callback
.)
Here is a version using objects:
// not all code is shown here for the sake of brevity
module.exports.handle = (event, context, cb) => {
const s3Sdk = new AWS.S3({ region: config.s3.region });
const s3Legacy = new S3(s3Sdk, config.s3.legacyBucketName);
const s3Cache = new S3Cache(s3Sdk, config.s3.cacheBucketName);
s3Legacy.putObject(key, event.body, callback);
s3Cache.putObject({
unitId: event.pathParameters.unitId,
timestamp: event.timestamp,
eventId: event.id,
eventFileName: event.id,
body: event.body
}, callback)'
}
class S3 {
constructor(s3Sdk, bucketName) {
this.s3Sdk = s3Sdk;
this.bucketName = bucketName;
}
putObject(key, body, callback) {
this.s3Sdk.putObject({ Bucket: this.bucketName, Key: key, Body: body }, (err, response) => {
callback(err, response);
});
}
}
class S3Cache extends S3 {
constructor(s3Sdk, bucketName) {
super(s3Sdk, bucketName);
}
putObject(params, callback) {
const key = this.toS3Key(
params.unitId,
this.toTimestampObject(params.timestamp),
params.eventId,
params.eventFileName
);
super.putObject(key, params.body, callback);
}
toS3Key(unitId, incidentTimestamp, incidentId, archiveIncidentObjectName) {
return ....
}
toTimestampObject(timestamp) {
return ....
}
}
Here is a version without using objects:
// not all code is shown here for the sake of brevity
module.exports.handle = (event, context, cb) => {
const s3 = new AWS.S3({ region: config.s3.region });
await s3.putObject({ Bucket: config.s3.legacyBucketName, Key: key, Body: event.body }).promise();
await s3.putObject({
Bucket: config.s3.cacheBucketName,
Key: toS3Key(event.unitId, toTimestampObject(event.timestamp), event.eventId, event.eventId),
Body: event.body
}).promise();
}
function toS3Key(unitId, incidentTimestamp, incidentId, archiveIncidentObjectName) {
return ....
}
function toTimestampObject(timestamp) {
return ....
}
In this version of the code, both the service orchestration code (write to s3Legacy
and if successful write to s3Cache
) and the s3Legacy
, s3Cache
, the code constructing the S3 key resides in one source file.
So this source file could change for multiple unrelated reasons. For example when we want to change the orchestration, change the order of writing to S3s or if we want to write to DynamoDB for example. The source file would also change if we need to change the S3 key schema.
We set a pattern here that every new addition would be added to this single source file which size would grow as a result and the list of reasons for a change would also grow.
While this example here is still small, calls only 2 services, I wonder whether extracting out the S3 code would set a better starting point for additional new development and enhancement.