7 ms·
In Typescript I've started doing interface IHasSaveProject { saveProject(...): ... } class HasSaveProject implements IHasSaveProject { saveProject(...)
by xthrowawayxx 4y ago
In Typescript I've started doing
interface IHasSaveProject {
saveProject(...): ...
}
class HasSaveProject implements IHasSaveProject {
saveProject(...): {...}
}
class Controller {
constructor(private readonly IHasSaveProject) {}
post(...) {
this
.hasSaveProject
.saveProject(...)
}
}
And basically have a different class for every function
In cases where it really makes sense to have multiple methods on a single class, I do this
interface IProjectService extends IProjectService.IHasGetProject ,IProjectService.IHasGetProject {}
namespace IProjectService {
export interface IHasGetProject {
getProject(...): ...
}
export interface IHasSaveProject {
saveProject(...): ...
}
}
class ProjectService implements IProjectService {
getProject(...): {...}
saveProject(...): {...}
}
class Controller {
constructor(private readonly projectService: IProjectService) {}
// or
// constructor(private readonly projectService:
// IProjectService.IHasGetProject
// & IProjectService.IHasaveProject) {}
// or
// constructor(
// private readonly hasSaveProject: IProjectSercice.IHasSaveProject
// private readonly hasGetProject: IProjectSercice.IHasGetProject
// ) {}
...
}
Seems to be working well for me. Lots of flexibility. Not sure how others like it though.
Please forgive my formatting
- lionkor 4y agoI dont think this makes much sense - a lot of them would likely share functionality and you could group them, like ISavable and just give that all save functions
- xthrowawayxx 4y agoIn my experience I prefer grouping by domain over grouping by abstraction An ISaveable interface would need to be generic but "save" functions might take different numbers of arguments, and of different types. I've found the advantage of interfaces to be dependency injection, where i can inject a different implementation of an interface without breakijg anything (eg save to s3, to google cloud storage, to filesystem, to memory), or a test version of the function/class. Abstractions like ISaveable on the service interface just make the code more fragile in my experience in an attempt to save some extra but simple lines of code. Granted it may make sense on active record model classes though.
- SebastianKra 4y agoI'll ignore the argument over whether a generic Save<T> (or even Repository<T>) type would be better. I'm unsure about DI in general [1], but using the Objectifier-Pattern just to appease the DI-System is bad. Likely, you can use `Symbol()` to indicate how your code should be wired. Decoupling your dependency seems reasonable, but you can do it much simpler. type GetProject = (...) => ... type ProjectCRUD = { get: GetProject, set: ... } constructor(private projectCrud: ProjectCrud) Depending on the situation, I would go even further and inline the type definition: constructor(args: { getProject: (...) => ..., setProject: (...) => ..., }) [1]: https://news.ycombinator.com/item?id=31547975 https://news.ycombinator.com/item?id=31547975 - I'd love to hear opinions on that.
- xthrowawayxx 4y ago>Decoupling your dependency seems reasonable, but you can do it much simpler. > type GetProject = (...) => ... > type ProjectCRUD = { get: GetProject, set: ... } > constructor(private projectCrud: ProjectCrud) Yep that is a more concise way of doing the same thing. I've considered that approach and it seems perfectly reasonable, even cleaner. The only reason I haven't is because constructors offer a more standardised approach to object creation for service type objects that other developers are familiar with, rather than higher order functions or function constructors. Although my method is kind of bespoke anyway so I could go either way. Another advantage of your approach is composing finer grained functions or objects with methods into more expansive services becomes delightfully easy. >[1]: https://news.ycombinator.com/item?id=31547975 https://news.ycombinator.com/item?id=31547975 - I'd love to hear opinions on that. I've tried to use TypeScriot DI containers... Oh I've tried.. But eventually they all just feel gross. These days I'm perfectly happy having a bootstrap / createServices method for the application, or with multiple entry points each requiring a subset of services with different configurations, different create<command>Services functions. Works well for CLI apps. Downside is when there are too many entry points with different depenencies, like an HTTP API, you don't want to create a bootstrap method for each entrypoint. In this case I create all services once on bootup. I pass the request context as method argument. Don't have a perfect way yet of doing request-level services like GraphQL caching. Currently I lazy load request level services on request context.
- gjvc 4y agoHaving the data in a "model" class and the controller implemented as a set of free functions operating on it works well.
- throwaway0x7E6 4y agoah, that's the reason why I don't put TS on my resume, despite having worked with it for years on personal projects. the number of 1000 LoC solutions for 100 LoC problems I saw in open source projects fills me with dread
- cutler 4y agoI'm really glad Perl was my first programming language back in 2000 and not Java or C#. The transition to Clojure was so much easier for me, not having done time in an OO prison. React succeeded in bucking the trend of OOP JS frameworks established by Microsoft and Google's promotion of Angular so they simply set their sights on converting JS itself into yet another OO monstrosity. Now we have none other than M$ as the major steward of Typesript and VS Code. As Lennon said, "Strange days indeed".
- xthrowawayxx 4y agoThis is true
- cutler 4y agoThere should be a firing squad for such abuse of Javascript. We were warned about what Typescript would turn Javasript into.
- xthrowawayxx 4y agoWhat's wrong with it? How do you mock functionality?
- cutler 4y agoThe same way you would in any other dynamic language such as Ruby or Python.
- xthrowawayxx 4y agoWhich is?