samedi 3 juin 2023

DTO to entity conversion: in service or controller, for manytoone owning side, should service accept DTO

Suppose I have a simple application with entities like these:

Dog.java:

@Entity(name = "dogs")
public class Dog  {
    @Id
    @SequenceGenerator(name = "dog_sequence",
                            sequenceName = "dog_sequence",
                            allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                            generator = "dog_sequence")
    private Long id;
  
    @Column(unique = true)
    private String name;

    // getters, setters, constructors
}

Command.java:

@Entity(name = "commands")
public class Command  {
    @Id
    @SequenceGenerator(name = "command_sequence",
                            sequenceName = "command_sequence",
                            allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                            generator = "command_sequence")
    private Long id;
  
    @Column(unique = true)
    private String name;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "dog_id", nullable = false)
    private Dog dog;

    // getters, setters, constructors
}

So a dog can know several commands, and each command is unique for a dog (belongs only to one dog).

I have a view (website page) that allows the admin to add new commands to each dog and also change the dog's name.

Once done, the view would send a POST request to /api/dog/edit with JSON like this:

{
  "commands": ["some command name", "another command name"],
  "id": 1,
  "name": "Updated Dog Name"
}

Now, because the Dog entity doesn't contain a List<Command> field, the controller can't map all of the JSON to the Dog entity, so I created a DogDto:

DogDto.java:

public class DogDto  {
  private Long id;
  private String name;
  private List<CommandDto> commands;  
  

  // getters, setters, constructors
}

and CommandDto:

CommandDto.java:

public class CommandDto  {
  private Long id;
  private String name;
  private DogDto dog;

  // getters, setters, constructors
}

Now my controller can map the JSON passed from view to the DogDto:

@RestController
@RequestMapping(path = "api/dog")
public class DogController {
    ...

    @PostMapping("edit")
    @CrossOrigin
    public ResponseEntity<DogDto> editDog(@RequestBody DogDto dogDto)  {
      ...
      Dog dog = mapper.map(dogDto, Dog.class);
      List<Command> commands = new ArrayList<>();
      for (CommandDto commandDto: dogDto.getCommands())  {
        Command command = mapper.map(commandDto, Command.class);
        commands.add(command);
      }
      ...
    }
}

Now I can map the DogDto to a Dog and Command entities. But what would be the best way to persist them? Currently my DogService has a

public boolean editDog(Dog dog) {...}

method that accepts a dog, tries to find it by ID:

Dog foundDog = this.dogRepository.findById(dog.getId());

and if the dog was found, update its name field and do this.dogRepository.save(foundDog);.

However I also need to save the commands. The Dog entity doesn't contain the list of commands. So I either need to change the editDog method signature to boolean editDog(DogDto dog) or do it in the controller like this:

for (Command command: commands)  {
  command.setDog(dogFoundById);
  this.commandService.createCommand(command);
}

I'm lost as to what to do here.

And I've also been wondering what if I had a more complex structure, with more nested entities:

public class A  {
  ...
  @OneToMany
  private List<B> bs;
}
public class C  {
  ...
  @ManyToOne
  private B b;
}

would the best design decision be different?

Aucun commentaire:

Enregistrer un commentaire