1
\$\begingroup\$

In a recent project my backend architecture consists of

  • Repositories (talking to database)
  • Services (handling business logic)
  • Controllers (handling incoming REST calls and providing according responses to the client)

I introduced DTO classes for representing the data that is returned by the controllers when a client calls a REST route. Because the services work with Entities, I need to somehow map those to my DTOs. The DTOs contain most of the properties of the according Entity, but can define additional properties as well.

First of all, I have the following Entities:

Image Entity

@Entity()
export class Image {
    id: number;
    name: string;

    @ManyToOne(...)
    parentFolder: Folder;

    @ManyToMany(...)
    tags: Tag[];
}

Tag Entity

@Entity()
export class Tag {
    id: number;
    label: string;

    @ManyToMany(...)
    images: Image[];
}

As you can see, there are also some relations (ManyToOne, ManyToMany), so my Entities contain references to other Entities. Same for according DTOs.

Next, you can see my shortened ImageController. In the method findOne, a service is asked to look for an Entity with given id in the database.

Image Controller

@Controller('image')
export class ImageController {
    constructor(
        private readonly imageService: ImageService,
        private readonly imageEntityToDtoMapper: ImageEntityToDtoMapper
    ) { }

    @Get(':id')
    async findOne(@Param('id') id): Promise<ImageDto> {
        const image: Image = await this.imageService.findOne(id);
        return this.imageEntityToDtoMapper.map(image);
    }
}

In order to let the controller return a DTO, the result is mapped by an ImageEntityToDtoMapper.

ImageEntityToDtoMapper

@Injectable()
export class ImageEntityToDtoMapper {
    constructor(
        private readonly folderService: FolderService,
        private readonly folderEntityToDtoMapper: FolderEntityToDtoMapper,
        @Inject(forwardRef(() => TagEntityToDtoMapper))
        private readonly tagEntityToDtoMapper: TagEntityToDtoMapper
    ) { }

    async map(entity: Image): Promise<ImageDto> {
        if (entity) {
            const parentFolderPath = await this.folderService.buildPathByFolderId(entity.parentFolder.id);
            const absolutePath = `${parentFolderPath}${path.sep}${entity.name}`;

            const dto = new ImageDto();
            dto.id = entity.id;
            dto.name = entity.name;
            dto.absolutePath = absolutePath;
            dto.parentFolder = await this.folderEntityToDtoMapper.map(entity.parentFolder);
            dto.tags = await this.tagEntityToDtoMapper.mapAll(entity.tags);
            return dto;
        }
    }
}

And here are my questions, all regarding ImageEntityToDtoMapper:

  1. Beside simply mapping properties id and name from one class to the other, I'm also "calculating" absolutePath, which is not contained in the original Entity, but only in DTO. Is this mapper class the correct place for that?
    Furthermore, for that reason I need to inject the FolderService to the mapper, which seems not quite correct to me...
  2. Because of the ImageEntities' relations to Folder and Tag, I need to call their mapper classes from the ImageEntityToDtoMapper. Is this the correct way to do it?
  3. Because I need to inject other Entities' mappers, I have a circular dependency between ImageEntityToDtoMapper and TagEntityToDtoMapper. And in my whole project, I have more cases where multiple mappers depend on each other.
    Although the library (NestJS) I'm using offers a way to handle this by using forwardRef, I would like to avoid those circular dependencies. Could you think of any possibility?

Thank you in advance - I really appreciate any feedback!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.