Skip to content

Latest commit

 

History

History

mvc-functional-oop

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

🧩 MVC + Functional OOP

#functional #mvc #oop

Open in StackBlitz

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}`
    }
  }
})