2

I'm building a use-case for creating blog posts, at first I thought of using an entity as a use-case parameter, and this entity would be created by the layer that calls Use Case, in my case a controller.

Controller:

//....

public function createPost(request: Request): void {

  const imagesToUpload: string[] = request.files('images');

  try {

    const PostEntity = new Post(..., imagesToUpload);
    const createdPost = this.createPost.create(PostEntity)

  } catch (exception) {
    //....
  }
}

Use Case:

export default class CreatePost {

  private PostRepository: PostRepository;
  private StorageAdapter: StorageAdapter;

  constructor(
    PostRepository: PostRepository,
    StorageAdapter: StorageAdapter
  ) {
    this.PostRepository = PostRepository;
    this.StorageAdapter = StorageAdapter;
  }

  public create(Post: Post): PostId {

    let uploadedImages: string[];

    {
      const images = Post.getImages()

      if (images.length > 0) {
        // upload images
        images.forEach(
          (image) => uploadedImages.push(this.StorageAdapter.upload(image))
        )
      }
    }

    // imutability broken
    Post.setImages(uploadedImages)

    return this.PostRepository.create(Post)
  }
}

Some problems I noticed:

1 - My Entity is being created on the controller without an ID

Although I could inject a repository or even directly use a factory to generate a PostId, it doesn't seem right for an outer layer to communicate directly with an inner layer, although I understand that it's okay for the infrastructure to know the domain.

2 - My entity is immutable, but some information is only available after some actions have taken place in my Use Case, such as the return of uploaded images URLs, so my entity may start with an invalid state (no images)

In this scenario where I'm breaking my entity's immutability, and initially creating it without an ID, does it make sense to use a DTO as a Use Case parameter?

2.2 - if I use a DTO, wouldn't the Use Case layer be coupled to the DTO, knowing its properties, getters, etc? or is it "OK" to create a specific DTO for this Use Case layer? such as: CreatePostDTO

2 Answers 2

1

Neither. Your use case method should define which parameters it requires. I will call this (typescript) class/interface CreatePost. In the controller you map your DTO (request.body) to the CreatePost object and then in the use case method you create the entity.

Edit: I forgot to say. If you are using some framework, it might already map request body to the CreatePost (class-validator and class-transformer come to mind, but this will add a library into your domain, which then means that your domain depends on a library. If you want to do hexagonal that is kind of a nono, but you will have to decide if you want this.). Technically CreatePost is your DTO then, but this does not matter, because it lives on the domain layer, the fact that API layer used it as a DTO is a coincidence.

Edit 2: I did not notice, that CreatePost was the name of your class that created post. I renamed it to a PostService, as the same class will probably also update it, delete it, etc. Here is the code

Controller:

//....

public function createPost(request: Request): void {

  const imagesToUpload: string[] = request.files('images');

  try {

    const newPost = new NewPost(..., imagesToUpload);
    const createdPost = this.postService.create(newPost)

  } catch (exception) {
    //....
  }
}
export class NewPost {
  // fields
  ...
  images: string[]
}

export default class CreatePost {

  private postRepository: PostRepository;
  private storageAdapter: StorageAdapter;

  constructor(
    PostRepository: PostRepository,
    StorageAdapter: StorageAdapter
  ) {
    this.postRepository = PostRepository;
    this.storageAdapter = StorageAdapter;
  }

  public async create(newPost: NewPost): PostId {
    const uploadedImages = await this.storageAdapter.batchUpload(newPost.getImages())
    
    const post = new Post(..., uploadedImages)

    return this.postRepository.create(post)
  }
}

EDIT 3: In java I would go about defining NewPost in two ways.

either like this:

public class CreatePost {
  ...

  public static class NewPost { ... }
}

or like this:

public class Post {
  private Post() {}

  public static class New { ... }
}

and then in the use case:

public PostId create(Post.New newPost) { ... }
9
  • sorry for the confusion, the CreatePost is the name of my UseCase/Service, but in your example, you created it as a DTO for the use-case/service layer, right? should I create the CreatePost DTO in the controller and then pass it to the service, but is that correct? since the service knows about the DTO. I questioned that because I don't know in which "layer" the definition of DTO stands, if it could be used in any layer, that's fine. Jun 12, 2021 at 16:34
  • about the "ugly" part of the image upload, the logic is encapsulated in the storageAdapter, but the method calling and the loop shouldn't be on the use-case? since it's part of its logic. Jun 12, 2021 at 16:34
  • Oh, I see. No, it is not a DTO, but a parameter object for the use case. You can create a seperate DTO in the controller and then map it into CreatePost parameter (you actually don't need to map it, because javascript doesn't care). Basically you controller knows about the parameter to the create method, but your create method does not know about the DTO. Does that make sense?
    – Blaž Mrak
    Jun 12, 2021 at 16:50
  • You could move this part of the logic to the storage adapter. I'll just correct in the code
    – Blaž Mrak
    Jun 12, 2021 at 16:51
  • I have cleaned up the example and kept the use case format that you were using. The DTO in the controller is basically the request.body. You then map the body into the NewPost parameter for the CreatePost.create. This is not a controller's DTO, but an object that you use instead of writing CreatePost.create(sender, images, recipient, ...). This object would exist even if controller did not.
    – Blaž Mrak
    Jun 12, 2021 at 17:05
-1

I think its unusual to create a class for a usecase and your example is muddled. But essentially you have two options, there is little difference between them.

  1. OOP put logic on classes

Here you have a BlogPost object with a Save() method. All the logic for saving the post goes under the save method, looping through the images etc.

You can have more object such as a Data Layer injected into the BlogPost to handle soem of the stuff, but you call them through BlogPost.Save()

  1. ADM put logic in services

Here you have a BlogPost object with no methods, only data and you put the logic in a second class BlogPostPersister.Save(BlogPost p). A bit like your UseCase object. Again you can inject data access layer and other objects as required, but it encapsulates looping through the images and whatever other 'business logic' you have.

I prefer 2, it's neater. Whichever way you choose though, you also have all the other framework objects. ie. controllers, UI etc.

Whether you need an extra Request object for example really depends on these framework objects and the detail of your implementation. Can you deserialise directly to a BlogPost object or do you have to write mapping code? Is you application running on a single machine and all these objects are held in memory so they don't need conversion? etc etc

I see you have edited and asked some specifc questions.

  • Q1. I create a class instance with no id in the controller

  • A1. That's presumably because you are using database generated Ids. Stop doing that and use GUIDs. You can then create them in the UI or Controller or business logic.

  • Q2. Entity is immutable, not image urls are generated after saving.

  • A2. Again, you could generate the URLs before saving, they just wouldn't work. But even if they are initially Null, this doesnt break immutability, you just have to Save() the BlogPost you have and Load() another.

  • Q3. DTO will be coupled to UI

  • A3. Here you are getting into the problem I outlined, multiple versions of Person. If its a problem switch to ADM and you can use interfaces to remove the coupling on the Services

1
  • well, it's a class because of OOP. the two suggestions seems like the difference between data mapper and active record, but I think that's only for the context of a CRUD, I'm also not depending on the framework ou implentations. Jun 12, 2021 at 16:46

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.