mercredi 22 juillet 2020

Designing data source agnostic models

I'm working on a full stack Typescript app, and am trying to

  1. Keep all of the models and services logic separated from any backing data source
  2. Keep data and behavior separated (i.e. method-less models)

But I'm having a hard time figuring out how to design these models when it comes to relationships.

Right now I have a core package, that exports some interfaces

interface Item {
  id: string;
  text: string;
  idProject: string;
}

interface Project {
  id: string;
  name: string;
  idSource: string;
}

interface Source {
  type: 'rss' | 'api';
  name: string;
}

interface ItemService {
  get(id: string): Promise<Item>
}

interface ProjectService {
  get(id: string): Promise<Project>
}

interface SourceService {
  get(id: string): Promise<Source>
}

interface ProjectItemLoaderService {
  constructor();
  loadItems(project: Project, source: Source): Promise<Item[]>;
}

and then in my server package, I use them like this

get('/project/:idProject/items', ({ idProject }) => {
  const project = projectService.get(idProject);
  const source = sourceService.get(project.idSource);
  const items = projectItemLoaderService.loadItems(project, source);
  ...
})

But from what I've been reading on this topic of well-abstracted models (DDD, hexagonal architecture), my models shouldn't be referring to relationships via ID, but should include the actual model, since things like ID are a storage-concept, and not a domain concept.

interface Item {
  id: string;
  text: string;
  project: Project
}

interface Project {
  id: string;
  name: string;
  source: Source
}

interface Source {
  type: 'rss' | 'api';
  name: string;
}

...

interface ProjectItemLoaderService {
  constructor();
  loadItems(project: Project): Promise<Item[]>;
}

...

get('/project/:idProject/items', ({ idProject }) => {
  const project = projectService.get(idProject);
  // Project already includes the Source model, not need to load it
  const items = projectItemLoaderService.loadItems(project);
  ...
})

This is more convenient to not have to constantly be loading nested models, but seems like it could get out of handle easily as the nesting gets deeper and deeper. The Project model should actually have a items: Item[] property, which could take a quite a long time to populate based on the backing storage. And it would then be cyclical, which is a whole other issue.

I'm basically looking for any advice on making data-agnostic models, and how services that handle those models should be given access its relationships. Maybe doing this sort of thing without going full DDD (use cases, commands, all that) is just not very feasible, and I should just be using source-couple ORM objects, but I really do like the idea of having the business logic layer being decoupled from the underlying data source.

Aucun commentaire:

Enregistrer un commentaire