Jak poradzić sobie z nullami przy tworzeniu dto?

Odpowiedz Nowy wątek
2018-11-07 19:20
0

Klasa ClientDto wygląda tak:

public final class ClientDto {

    private Long id;
    private String login;
    private String nip;
    private String companyName;

    private String contactAddressCity;
    private String contactAddressStreet;
    private String contactAddressHouseNumberEtc;
    private String contactAddressPostalCode;

    private String registerAddressCity;
    private String registerAddressStreet;
    private String registerAddressHouseNumberEtc;
    private String registerAddressPostalCode;
// ...

jest tworzona z Client w której adresy nie muszą być podane, więc pojawia się nullpointerexception gdy mapuję dto tak:

@Override
    public ClientDto clientToDto(Client client) {
        ClientDto clientDto = new ClientDto();
        clientDto.setId(client.getId())
            .setLogin(client.getUser().getLogin())
            .setCompanyName(client.getCompanyName())
            .setContactAddressCity(client.getContactAddress().city)
            .setContactAddressHouseNumberEtc(client.getContactAddress().houseNumberEtc)
            .setContactAddressPostalCode(client.getContactAddress().postalCode)
            .setContactAddressStreet(client.getContactAddress().street)
            .setNip(client.getNip())
            .setRegisterAddressCity(client.getRegisterAddress().city)
            .setRegisterAddressHouseNumberEtc(client.getRegisterAddress().houseNumberEtc)
            .setRegisterAddressPostalCode(client.getRegisterAddress().postalCode)
            .setRegisterAddressStreet(client.getRegisterAddress().street);
        return clientDto;
    }

np. jak nie ma adresu to powinienem dostać jsona:

{
        "id": 2,
        "login": null,
        "nip": "9510525054",
        "companyName": "Janusz Soft sp. z o.o.",
        "contactAddressCity": null,
        "contactAddressStreet": null,
        "contactAddressHouseNumberEtc": null,
        "contactAddressPostalCode": null,
        "registerAddressCity": null,
        "registerAddressStreet": null,
        "registerAddressHouseNumberEtc": null,
        "registerAddressPostalCode": null
    }

Jak to sprytniej obsłużyć niż

@Override
    public ClientDto clientToDto(Client client) {
        ClientDto clientDto = new ClientDto();
        clientDto.setId(client.getId())
            .setLogin(client.getUser().getLogin())
            .setCompanyName(client.getCompanyName())
            .setNip(client.getNip());

             if(client.getContactAddress == null) {
                clientDto.setContactAddressCity(client.getContactAddress().city)
                    .setContactAddressHouseNumberEtc(client.getContactAddress().houseNumberEtc)
                    .setContactAddressPostalCode(client.getContactAddress().postalCode)
                    .setContactAddressStreet(client.getContactAddress().street)
             }

            if(client.getRegisterAddress == null) {
                clientDto.setRegisterAddressCity(client.getRegisterAddress().city)
                    .setRegisterAddressHouseNumberEtc(client.getRegisterAddress().houseNumberEtc)
                    .setRegisterAddressPostalCode(client.getRegisterAddress().postalCode)
                    .setRegisterAddressStreet(client.getRegisterAddress().street);
            }
        return clientDto;
    }

. W Encji jak dam Optionale do getterozy to mi się nic nie rozwali? A co jak mam publiczne metody, mam je robić Optionalami?
Krew mnie zalewa od tego JPA. Po 5 razy przepisuje się to samo.

edytowany 3x, ostatnio: Julian_, 2018-11-07 19:24

Pozostało 580 znaków

2018-11-08 09:33
eL
0

Nazywaj testy jakoś sensownie...

Julian_ napisał(a):

  public void testEmpty() {
      // given
      AddressDto dto = new AddressDto();

      // when
      Address addressFromDto = mapper.dtoToNewSource(dto);

      // then
      assertNotNull(addressFromDto);
  }

Co to to ma być?
Abstrachując od nazwy która nie ma sensu to sprawdzasz czy nie jest nullem a nie czy jest pusty.

Po drugie jak masz niżej taki test:

Julian_ napisał(a):
  public void testDtoToNewSource() {
      // given
      AddressDto dto = new AddressDto();
      dto.setCity("a").setHouseNumberEtc("1b").setStreet("c").setPostalCode("00-001");

      // when
      Address addressFromDto = mapper.dtoToNewSource(dto);

      // then
      assertEquals("a", addressFromDto.city);
      assertEquals("1b", addressFromDto.houseNumberEtc);
      assertEquals("c", addressFromDto.street);
      assertEquals("00-001", addressFromDto.postalCode);
  }

To po kiego Ci ten pierwszy? Ten drugi jak się wykona to zrobi w zasadzie to samo co ten pierwszy plus kilka dodatkowych rzeczy. Wywal ten pierwszy i będziesz miał mniej testów do utrzymania bo jak ich narobisz kilkaset to potem boli jak trzeba tyle zmieniać.

Julian_ napisał(a):
         assertEquals("a", addressFromDto.city);
  assertEquals("1b", addressFromDto.houseNumberEtc);
  assertEquals("c", addressFromDto.street);
  assertEquals("00-001", addressFromDto.postalCode);

Z dostępem do tych pól na pewno wszystko jest okej?

Julian_ napisał(a):
 @Test
  public void testDtoToUpdatedSource() {
    testDtoToNewSource();
  }

WAT :o ?!

edytowany 2x, ostatnio: eL, 2018-11-08 09:36

Pozostało 580 znaków

2018-11-08 12:13
2

Odnośnie zwracania dto prosto z zapytania to można to zrobić mniej więcej tak (zupełnie zgaduję jakie masz mapowanie):

        Query query = session.createQuery("SELECT new your.package.ClientDto(c.id, c.login, c.nip, c.companyName, a1.city, a1.street, a2.city, a2.street) "
            + " FROM Client c "
            + " LEFT JOIN c.contactAddress a1"
            + " LEFT JOIN c.registerAddress a2");
        List<ClientDto> result = query.getResultList();

Do tego potrzebujesz odpowiedni konstruktor w ClientDto, np. coś w rodzaju:

        public ClientDto(String id, String login, String companyName, String city1, String street1, String city2, String street2) {
            this.id = id;
            this.login = login;
            this.companyName = companyName;
            this.contactAddress = new AddressDto(city1, street1);
            this.registerAddress = new AddressDto(city2, street2);
        }
edytowany 5x, ostatnio: Seti87, 2018-11-08 12:18

Pozostało 580 znaków

2018-11-08 12:22
0
eL napisał(a):

Nazywaj testy jakoś sensownie...

Julian_ napisał(a):
```java @Test
public void testEmpty() {
    // given
    AddressDto dto = new AddressDto();

    // when
    Address addressFromDto = mapper.dtoToNewSource(dto);

    // then
    assertNotNull(addressFromDto);
}

Co to to ma być?
Abstrachując od nazwy która nie ma sensu to sprawdzasz czy nie jest nullem a nie czy jest pusty.

Po drugie jak masz niżej taki test:

Julian_ napisał(a):
```[email protected]
public void testDtoToNewSource() {
    // given
    AddressDto dto = new AddressDto();
    dto.setCity("a").setHouseNumberEtc("1b").setStreet("c").setPostalCode("00-001");

    // when
    Address addressFromDto = mapper.dtoToNewSource(dto);

    // then
    assertEquals("a", addressFromDto.city);
    assertEquals("1b", addressFromDto.houseNumberEtc);
    assertEquals("c", addressFromDto.street);
    assertEquals("00-001", addressFromDto.postalCode);
}

To po kiego Ci ten pierwszy? Ten drugi jak się wykona to zrobi w zasadzie to samo co ten pierwszy plus kilka dodatkowych rzeczy. Wywal ten pierwszy i będziesz miał mniej testów do utrzymania bo jak ich narobisz kilkaset to potem boli jak trzeba tyle zmieniać.

w testEmpty sprawdzam czy da się przekonwertować obiekt z pustymi wartościami, jesteś pewien, że wywalić?

Julian_ napisał(a):
         assertEquals("a", addressFromDto.city);
    assertEquals("1b", addressFromDto.houseNumberEtc);
    assertEquals("c", addressFromDto.street);
    assertEquals("00-001", addressFromDto.postalCode);

Z dostępem do tych pól na pewno wszystko jest okej?

klasa Address wygląda tak o:

@Entity
@Immutable
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "city", "street", "houseNumberEtc", "postalcode" }))
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(updatable = false)
    public final String city;

    @Column(updatable = false)
    public final String street;

    @Column(updatable = false)
    public final String houseNumberEtc;

    @Pattern(regexp = "^\\d{2}-\\d{3}$")
    @Column(updatable = false)
    public final String postalCode;

    @OneToOne(mappedBy="address")
    private Location area;

    /**
     * Arguments of the class are immutable but JPA always needs a default
     * constructor. Documentation of the Hibernate says: "The entity class must have
     * a public or protected no-argument constructor."
     */
    protected Address() {
        this.city = null;
        this.street = null;
        this.houseNumberEtc = null;
        this.postalCode = null;
    }

    public Address(String city, String street, String houseNumberEtc, String postalCode) {
        this.city = city;
        this.street = street;
        this.houseNumberEtc = houseNumberEtc;
        this.postalCode = postalCode;
    }

    public Long getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Address [" + city + street + houseNumberEtc + postalCode + "]";
    }
}

Pozostało 580 znaków

2018-11-08 17:45
0

Napisz sobie makro w Scali i wtedy sobie zaszyjesz w nim dowolne reguły a nawet obsługę własnych adnotacji.

Pozostało 580 znaków

2018-11-08 18:55
0

możecie obczaić githuba:

kod:
https://github.com/trolololol[...]/api/mapper/ClientMapper.java

test:
https://github.com/trolololol[...]/mapper/ClientMapperTest.java

tak zrobiłem.

edytowany 1x, ostatnio: Julian_, 2018-11-08 18:57

Pozostało 580 znaków

2018-11-08 19:02
0

Tak poza tematem. Tworzenie mapperów wymaga tworzenia getterów, co za tym idzie podobnej struktury wewnętrznej obiektów. Czy nie jest to trochę gwałt na OOP? Czy nie lepiej żeby obiekt umiał sam się przetransferować do DTO, bez informowania świata o swojej implementacji?
Matka rodzi dziecko sama, lekarz nie wyrywa kawałków płodu i nie składa potem tego w coś żywego.


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.

Pozostało 580 znaków

2018-11-08 19:08
0
danek napisał(a):

Tak poza tematem. Tworzenie mapperów wymaga tworzenia getterów, co za tym idzie podobnej struktury wewnętrznej obiektów. Czy nie jest to trochę gwałt na OOP? Czy nie lepiej żeby obiekt umiał sam się przetransferować do DTO, bez informowania świata o swojej implementacji?
Matka rodzi dziecko sama, lekarz nie wyrywa kawałków płodu i nie składa potem tego w coś żywego.

Tylko co jeśli dto ma być okrojoną wersją domeny?

Obczaj ile mam zmiennych: https://github.com/trolololol[...]n/deliverp/domain/Client.java

a pokazuję tlyko te: https://github.com/trolololol[...]liverp/api/dto/ClientDto.java

Pokaż pozostałe 2 komentarze
a obiekt nie może mieć metody toDTO() i juz? - danek 2018-11-08 19:14
może, ale a co z single responsilbity? - Julian_ 2018-11-08 19:15
@danek: a jak byś zrobił konwersje DTO na jakiś obiekt domenowy? - eL 2018-11-09 07:16
@danek: Druga sprawa to jak obsłużysz wiele różnych DTO? Możesz mieć przecież co widok trochę inny DTO. - eL 2018-11-09 12:14
@eL: hm, może by zadziałało zamiast konstruktora metoda statyczna fromDTO(DTO dto) czy coś w ten desen. - danek 2018-11-09 13:28

Pozostało 580 znaków

2018-11-09 01:41
1

Może użycie czegoś do pomocy? na przykład
http://mapstruct.org/

Widzę że już się pojawił ten mapStruct wcześniej i skupiłem się na ostatnim poście a nie problemie z początku.
Widzę że przeszedłeś z totalnie płaskiej struktury na Dto z Dtosami, to może coś w stylu

addressDto = Optional.ofNullable(address).map(AddressMapper::toDto);

Ten Optional tworzony tak dla przykładu, możesz też zawsze sobie zwracać optionala, albo po prostu w metodzie mapującej sprawdzać czy argument nie jest nullem

edytowany 1x, ostatnio: Kermii, 2018-11-09 01:53

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0