I am working with classes right now and I got curious about the way the work with them should look like.
Maybe if you can recommend some good and descriptive books on the topic it would be great!
Questions:
-
Now I am working on the backend and I am trying to use classes everywhere I find it appropriate –
services
,endpoints
, others. I have someAngular
experience and there I always provide new services or whatsoever it is via constructor making kinda injection into the class. Is that the pattern I should always inject the stuff into the class using the constructor? -
Let’s say I’ve got a very simple class
imageService
:
import { PutObjectRequest } from "@aws-sdk/client-s3"
import { Upload } from "@aws-sdk/lib-storage"
import { Types } from "mongoose"
import env from "../env"
import { logMethod } from "../helpers"
import { debug } from "../utils/debug"
import { S3Client } from '@aws-sdk/client-s3'
export interface ImageUploadParams {
Body: PutObjectRequest["Body"],
Key: string,
ContentType: string,
Bucket?: string,
ACL?: string,
}
class ImageService {
private readonly s3: S3Client
constructor() {
this.s3 = new S3Client({
credentials: {
accessKeyId: env.s3.accessKeyId,
secretAccessKey: env.s3.secretAccessKey,
},
region: env.s3.region,
})
}
private readonly allowedTypes: string[] = [
'image/jpeg',
'image/png',
]
private generateName({ id, prefix }: { id: string | Types.ObjectId, prefix?: string }) {
return `${prefix ? prefix + '-' : ''}image-${id}-${Date.now()}`
}
generate({ id, prefix }: { id: string | Types.ObjectId, prefix?: string }) {
const name = this.generateName({ id, prefix })
return {
key: name,
link: `https://${env.s3.bucket}.s3.${env.s3.region}.amazonaws.com/${name}`
}
}
extractKey(link: string) {
return link.split('/').pop()
}
checkType(mimetype: string) {
return this.allowedTypes.includes(mimetype)
}
@logMethod
async uploadImage({ Body, Key, ContentType, Bucket, ACL }: ImageUploadParams) {
const upload = new Upload({
client: this.s3,
params: {
Body,
Key,
ContentType,
Bucket: Bucket ?? env.s3.bucket,
ACL: ACL ?? 'public-read',
},
})
if(!env.isProd) {
upload.on("httpUploadProgress", (progress) => {
if(progress && progress.loaded && progress.total) {
const percent = (progress.loaded / progress.total) * 100
debug(`image '${Key}' uploaded: ${percent}%`)
}
})
}
await upload.done()
}
}
export const imageService = new ImageService()
As you can see I am always instantiating the class and exporting outside (making it available for others). Is it a good practice to use classes like this? Or there is some kind of other flows or technics to make classes available?
-
Every method in a class should encapsulate some unit logic. What about the way of working with the class. Let’s say I am using the class above and I need to upload an image and I should interact with the class
imageService
making three different requests –imageService.generate
,imageService.checkType
,imageService.uploadImage
, and so on, and putting all the things together in the class it requires all of that. Am I right? So the class provides the abstractionsthe little unit tools
you can cooperate with and put up some own stuff, without anything concrete like the whole operation froma
toz
. Maybe there are some principals or something -
What about errors? Let’s say I am checking the image type and it is not valid. Should I throw an Error right away, or in this pattern I should just return the error to the class that invoked the method and it should decide what to do? If the last, there is a change of a bug or something. Do classes responsible for throwing errors, or just responding and delegating that on the classes that invoked them?
The questions must be very silly and vague, but any thoughts are welcomed