#functional
#mvc
#oop
MVC architecture with functional OOP design.
Following-up on function-resolve, I've been thinking about how to combine the simplicity of functional programming and the power of object-oriented design to better organize an application's architecture through domain-driven design, having inversion of control without the overhead currently necessary with JavaScript for the functional design proposed in function-resolve. Best case scenario would be JavaScript having context receivers. See full example here in StackBlitz. This architecture is being used in cot-perspective. This design only manages inversion of control, state should be handled by an independent solution like TanStack Query or Redux.
import { Record } from 'immutable'
import memoize from 'lodash/memoize'
import { Named, User } from '../model'
class FormatController {
getFullName (item: Named) {
return `${item.firstName} ${item.lastName}`
}
}
class UserRecord extends Record(new User()) {
constructor (state: Partial<User>, private ctrl: Controller) {
super(state)
}
get fullName () {
return this.ctrl.format.getFullName(this)
}
}
class ModelController {
protected types = { UserRecord }
constructor (private ctrl: Controller) {}
getUser (state: Partial<User> = {}): UserRecord {
return new this.types.UserRecord(state, this.ctrl)
}
getCustomController = memoize(() => {
return new Controller({
...this.ctrl.dependencies,
FormatController: class CustomFormatter extends FormatController {
getFullName (item: Named) {
return `${super.getFullName(item)} Bravo`
}
}
})
})
getCustomUser (state: Partial<User> = {}): UserRecord {
return new this.types.UserRecord(state, this.getCustomController())
}
}
const defaultDependencies = Object.freeze({
FormatController,
ModelController,
})
class Controller {
constructor (public dependencies = defaultDependencies) {
this.format = new dependencies.FormatController()
this.model = new dependencies.ModelController(this)
}
format: FormatController
model: ModelController
}
export const classController = new Controller()
export const customClassController = new Controller({
...defaultDependencies,
FormatController: class CustomFormatter extends FormatController {
getFullName (item: Named) {
return `${item.lastName}, ${item.firstName}`
}
}
})