node.js - Injiserer tjeneste i domenobjektet nok en gang


Jeg har et spesifikt domene som opererer på geografiske data. Jeg implementerer dette prosjektet i TypeScript og NodeJS og har følgende klasser:

  • Point - verdiobjekt som inneholder breddegrad og lengdegrad
  • Område - verdiobjekt som inneholder et sett med punkter som en formdefinisjon
  • Sektor - enhet (den lagres ikke, men den kan endres) - som inneholder område og sett med punkter som ligger innenfor den

Nå trenger jeg å implementere en metode kalt isPointInside(point: Point) som beregner om det oppgitte punktet passer innenfor området eller ikke. Jeg vil ikke implementere det selv, fordi det finnes biblioteker som kan gjøre det for meg. Akkurat nå har jeg bestemt meg for å bruke denne: https://github.com/manuelbieh/Geolib.

Jeg bruker også et IoC-rammeverk for TypeScript kalt inversifyjs, ingenting fancy - gir DI gjennom annotasjoner.

Så jeg laget et grensesnitt kalt "GeoService" som tilbyr metoder ansvarlige for geografiske beregninger, og jeg har tenkt å bruke det ganske mye i det domenemodellen. Ved å bruke inversify ga jeg en GeoLib-adapter som implementerer mitt grensesnitt, og jeg vil virkelig bruke det på en eller annen måte fra innsiden av min Area-klasse.

Så det er det første problemet, men det er ikke slutten på mine kamper :-).

Jeg har en annen klasse, kalt SectorGrid, som inneholder et rutenett av sektorer (hver sektor er kvadratet i dette bruksområdet) i en todimensjonal datastruktur. SectorGrid har en metode addPoint(point: Point). Ansvarsområdet til den metoden er å finne en sektor som det oppgitte punktet passer i, og hvis den ikke finner en, å opprette den. Nå trenger den ikke bare GeoService for å beregne hvor det opprinnelige punktet til sektoren skal være (avstand fra gridens sentrum), men den må også opprette en sektor - og der støtte jeg på problemet mens jeg skrev tester, det var for mye logikk, og jeg bestemte meg for å gi en slags sektorfabrikk til SectorGrid, ikke bare for å forenkle tester, men også for å kapsle inn sektors opprettelseslogikk (det er ganske komplisert). Så en annen tjeneste å injisere nå, og ingen anelse om hvordan jeg skal gjøre det på en måte som ikke vil føre til problemer.

Foreløpig har jeg bare injisert de to tjenestene som statiske egenskaper i disse klassene, men det er ikke noe jeg er stolt av og jeg ser etter andre alternativer.

Jeg vet at designet mitt kan være overkomplisert, og jeg leter etter en måte å forenkle det på, men jeg vil ikke ende opp i Anemic Domain Models-land. Å ha noe som et Area-objekt og ikke kunne legge geografiske beregninger inni det høres akkurat ut som en anemisk modell for meg.

Jeg har også lest ganske mange diskusjoner om å injisere tjenester i enheter, men ingen av dem tilfredsstilte meg, da de enten kom til konklusjoner som «ikke gjør dette» eller «bare gjør dette og ikke bry deg», eller ga løsninger som domenebegivenheter som ikke passer i det hele tatt i mitt tilfelle.

Svar #1

En vanlig måte å håndtere tilfeller der domeneobjekter trenger å samarbeide med tjenester på, er å injisere disse tjenestene på metodenivå og anvende ISP-prinsippet for å sikre at avhengighetene ikke er bredere enn nødvendig.

F.eks. addPoint(point: Point, geoService: GeoService)

En annen vanlig måte å håndtere problemet på er å løse avhengigheten fra applikasjonstjenesten og sende resultatet inn i aggregatmetoden, men når denne tilnærmingen lekker for mye logikk inn i applikasjonslaget, bør du sannsynligvis bruke tjenesteinjeksjon på metodenivå i stedet.

men kunne du utdype litt mer om den siste løsningen

Vel, forestill deg at et Project-aggregat må justere sin fullføringsstatus og prosent basert på sine tilknyttede Task-aggregater på en til slutt konsistent måte. For å gjøre dette må Project-aggregatet finne ut hvor mange oppgaver som er fullført så langt.

I stedet for å sende inn en TaskRepository/TaskCompletionSummaryProvider til Project.adjustCompletionState-metoden, kan du løse avhengigheten på applikasjonslagets nivå.

var project = projectRepository.projectOfId(someProjectId);
var taskCompletionSummary = taskRepository.taskCompletionSummaryOfProject(project.id());
project.adjustCompletionState(taskCompletionSummary);


class Project {
    public void adjustCompletionState(TaskCompletionSummary summary) {
        //The following line could be seen as defensive programming. You could also trust that the application layer is doing it's job correctly. It wouldn't be required at all if a `TaskCompletionSummaryProvider` service would be injected directly instead.
        if (this.id != summary.projectId()) throw new InvalidOperationException('Wrong summary for project');

        if (summary.allCompleted()) this.completionState = ProjectCompletionState.COMPLETED;
        else this.completionState = ProjectCompletionState.inProgress(summary.completionPercentage());
    }
}

Relaterte artikler