What are domain objects in java

Domain classes¶

A domain class represents a table column and it allows you to handle the column value as a Java object. In the Doma framework, a domain means all the values which a data type may contain. In short, a domain class is a user defined class that can be map to a column. The use of the domain classes is optional.

Every domain class is either an internal domain class or an external domain class.

Internal domain classes¶

The internal domain class must be annotated with @Domain . The @Domain ’s valueType element corresponds to a data type of a column. Specify any type of Basic classes to the valueType element.

Instantiation with a constructor¶

The default value of the @Domain ’s factoryMethod element is new . The value new means that the object of annotated class is created with a constructor.

@Domain(valueType = String.class) public class PhoneNumber  private final String value; public PhoneNumber(String value)  this.value = value; > public String getValue()  return value; > public String getAreaCode()  . > > 

In Java 14 and later version, you can annotate records with @Domain :

@Domain(valueType = String.class, accessorMethod = "value") public record PhoneNumber(String value)  public String getAreaCode()  . > > 

To annotate records with @Domain is a little redundant, because you must specify some properties to @Domain such as valueType . Instead of @Domain , you can annotate records with @DataType :

@DataType public record PhoneNumber(String value)  public String getAreaCode()  . > > 

Instantiation with a static factory method¶

To create the object of annotated class with a static factory method, specify the method name to the @Domain ’s factoryMethod element.

The method must be static and non-private:

@Domain(valueType = String.class, factoryMethod = "of") public class PhoneNumber  private final String value; private PhoneNumber(String value)  this.value = value; > public String getValue()  return value; > public String getAreaCode()  . > public static PhoneNumber of(String value)  return new PhoneNumber(value); > > 

With a static factory method, you can apply the @Domain annotation to enum types:

@Domain(valueType = String.class, factoryMethod = "of") public enum JobType  SALESMAN("10"), MANAGER("20"), ANALYST("30"), PRESIDENT("40"), CLERK("50"); private final String value; private JobType(String value)  this.value = value; > public static JobType of(String value)  for (JobType jobType : JobType.values())  if (jobType.value.equals(value))  return jobType; > > throw new IllegalArgumentException(value); > public String getValue()  return value; > > 

Using type parameters in internal domain classes¶

All internal domain class declarations have type parameters:

@Domain(valueType = int.class) public class IdentityT>  private final int value; public Identity(int value)  this.value = value; > public int getValue()  return value; > > 

When you create the object of annotated class with a static factory method, the method declaration must have same type parameters that are declared in the class declaration:

@Domain(valueType = int.class, factoryMethod = "of") public class IdentityT>  private final int value; private Identity(int value)  this.value = value; > public int getValue()  return value; > public static T> IdentityT> of(int value)  return new IdentityT>(value); > > 

External domain classes¶

This feature allows you to define arbitrary classes as domain classes, even if the classes can be annotated with the @Domain annotation.

To define external domain classes, you have to do as follows:

  • Create a class that implements org.seasar.doma.jdbc.domain.DomainConverter and annotate @ExternalDomain to the class
  • Create a class that is annotated with @DomainConverters
  • Specify the class annotated with @ExternalDomain to the @DomainConverters ’s value element
  • Specify the full qualified name of the class annotated with @DomainConverters to the option of Annotation processing

Suppose, for instance, there is the PhoneNumber class that you can change:

public class PhoneNumber  private final String value; public PhoneNumber(String value)  this.value = value; > public String getValue()  return value; > public String getAreaCode()  . > > 

First, to define the PhoneNumber class as an external domain class, create following class:

@ExternalDomain public class PhoneNumberConverter implements DomainConverterPhoneNumber, String>  public String fromDomainToValue(PhoneNumber domain)  return domain.getValue(); > public PhoneNumber fromValueToDomain(String value)  if (value == null)  return null; > return new PhoneNumber(value); > > 

Then create following class and specify the above class to the @DomainConverters ’s value element:

@DomainConverters( PhoneNumberConverter.class >) public class DomainConvertersProvider  > 

Finally, specify the full qualified name of the above class to the option of Annotation processing . If you use Gradle, specify the option in the build script as follows:

compileJava  options  compilerArgs = ['-Adoma.domain.converters=example.DomainConvertersProvider'] > > 

Using type parameters in external domain classes¶

All external domain class declarations have type parameters:

public class IdentityT>  private final int value; public Identity(int value)  this.value = value; > public int getValue()  return value; > > 

In the DomainConverter implementation class, specify a wildcard ? as type arguments to the external domain class:

@ExternalDomain public class IdentityConverter implements DomainConverterIdentity, String>  public String fromDomainToValue(Identity domain)  return domain.getValue(); > @SuppressWarnings("rawtypes") public Identity fromValueToDomain(String value)  if (value == null)  return null; > return new Identity(value); > > 

Example¶

The Domain classes showed above are used as follows:

@Entity public class Employee  @Id IdentityEmployee> employeeId; String employeeName; PhoneNumber phoneNumber; JobType jobType; @Version Integer versionNo(); . > 
@Dao public interface EmployeeDao  @Select Employee selectById(IdentityEmployee> employeeId); @Select Employee selectByPhoneNumber(PhoneNumber phoneNumber); @Select ListPhoneNumber> selectAllPhoneNumber(); @Select Employee selectByJobType(JobType jobType); @Select ListJobType> selectAllJobTypes(); > 

© Copyright 2018, Toshihiro Nakamura Revision 6ed6e702 .

Источник

Domain Object with Lombok: Боевая классика

Domain Object (рус. «Доменный объект») — один из наиболее популярных подходов к использованию тестовых данных непосредственно в логике скриптов. На данный момент является одним из самых популярных и распространенных approach’ей, благодаря своей простоте, понятности и логичности.

Применим во всех видах автоматизации функционального тестирования (End-to-End, API, Integration), в независимости от проверяемой платформы, будь то Web, Mobile, или Desktop.

ВАЖНО: не стоит путать Domain Object с Data Transfer Object (DTO). Это абсолютно разные подходы, которые применяются в разных сферах.

image

Из другого названия подхода — «Business Object» — становится понятно, что это некая абстракция, представляющая собой модель и описание объекта, который важен именно для понимания и функционирования бизнес-логики приложения. Он не способен выполнять никаких функций, кроме «переноса» полей и их значений одного конкретного business-unit’а.

Как это выглядит

В качестве примера, возьмем любое приложение, где предусмотрено создание аккаунта пользователя. Именно пользователь и становится нашим доменным объектом. Практически во всех случаях, у юзера должны быть логин и пароль:

// Создаем доменный класс User public class User < // Объявляем, что User может содержать значение Login private String login; // Объявляем, что User может содержать значение Password private String password; >

Особенно внимательные заметят, что все внутренние поля являются private. Задавать и считывать значения напрямую из полей объекта считается плохой практикой. Вместо этого принято использовать всем хорошо известные геттеры и сеттеры:

public class User < private String login; private String password; // Интерфейс для присваивания значения полю login public void setLogin(String login) < this.login = login; >// Метод для получения значения login public String getLogin() < return this.login; >// Аналогично для пароля public void setPassword(String password) < this.password = password; >public String getPassword() < return this.password; >> 

Теперь, мы получили доступ к полям, и можем задавать и считывать их значения. Но в данном примере у нас используется только два поля. А что произойдет, если этих полей будет пятнадцать? Верно, класс User разрастется до невиданных размеров, благодаря бесконечной копипасте getter’ов и setter’ов. Как же мы будем с этим справляться?

Магия Lombok

Здесь нам на помощь приходит Project Lombok — популярная библиотека, которая позволяет сократить код в несколько раз, избежать copy/paste-страданий, и значительно уменьшить количество времени на написание Data Object-классов. Несколько полезных ссылок:

import lombok.Getter; import lombok.Setter; // Объявляем геттеры для всех полей класса User @Getter // Объявляем сеттеры для всех полей класса User @Setter public class User

Просто, быстро, удобно. Таким образом, наш код становится гораздо читабельнее, классы — лаконичнее, а разработка — быстрее.

Применяем в тесте

Хороший тест — это тест, в котором четко разделены тестовые данные, тестовая логика, и ее реализация.

Попробуем залогинить нашего пользователя. Создаем класс с тестовыми данными, и «наполняем» ими нового User’а:

// Создаем Test Data класс public class TestDataUser < // Объявляем private-константы с тестовыми данными private final static String DEFAULT_LOGIN = "vasiliy_pupkin"; private final static String DEFAULT_PASSWORD = "q1w2e3"; // Реализуем статический метод для получения дефолтного юзера public static User getDefaultUser() < // Создаем "чистого" пользователя User user = new User(); // Используем сеттер для присваивания значения Login user.setLogin(DEFAULT_LOGIN); // Используем сеттер для присваивания значения Password user.setPassword(DEFAULT_PASSWORD); // Возвращаем объект класса User, наполненного нужными нам данными return user; >> 

Описываем модель Login-страницы:

public class LoginPage < // Создаем public-метод логина, который принимает объект User public void loginUser(User user) < // Через геттер получаем логин и передаем его в метод enterLogin() enterLogin(user.getLogin()); // Аналогично с паролем enterPassword(user.getPassword()); >private void enterLogin(String login) < this.loginInput.sendKeys(login); >private void enterPassword(String password) < this.passwordInput.sendKeys(password); >> 

Осталось только реализовать тестовый класс:

Подведем итоги

В финале мы получаем аккуратную архитектуру проекта, с четким разделением логики, данных, и реализации. Lombok помогает избавиться от дублирования кода, Domain Object замечательно вписывается в философию Page Object, а поддержка кода становится сплошным удовольствием.

Должны же быть минусы?

  1. Иммутабельность. А точнее, ее отсутствие. В этом данный подход с использованием Lombok проигрывает основном конкуренту — Builder (статья на Хабре). Зато, на мой взгляд, приведенный выше код является более «чистым», понятным, и эстетически приятным, по сравнению с нескончаемой цепочкой в билдере и нагромождением методов в Object-классе.
  2. Увеличение complexity там, где это не нужно. Не столько минус, сколько небольшое напоминание. Любой подход или паттерн имеет проблематику и должен решать некую проблему. Не стоит пытаться использовать Data Object в юнит-тесте, который всего лишь проверяет, что 2+2=4.

P.S. Расскажите в комментариях, какие подходы вы используете в работе. Будет очень интересно почитать.

Источник

Читайте также:  Python org downloads windows 10
Оцените статью