JavaRush /Java блогу /Random-KY /REST API жана маалыматтарды текшерүү
Денис
Деңгээл
Киев

REST API жана маалыматтарды текшерүү

Группада жарыяланган
Биринчи бөлүккө шилтеме: REST API жана кийинки тест тапшырмасы Ооба, биздин колдонмо иштеп жатат, биз андан кандайдыр бир жооп ала алабыз, бирок бул бизге эмне берет? Эч кандай пайдалуу иш жасаbyte. Айтылгандан кийин, пайдалуу нерсени ишке ашыралы. Биринчиден, build.gradle'ге бир нече жаңы көз карандылыктарды кошолу, алар бизге пайдалуу болот:
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Жана биз иштеп чыгууга тийиш болгон чыныгы маалыматтардан баштайбыз. Келгиле, туруктуулук топтомубузга кайрылып, an objectти толтуруп баштайлы. Эсиңизде болсо, биз аны бир гана талаа менен калтырганбыз, андан кийин авто `@GeneratedValue(strategy = GenerationType.IDENTITY)` аркылуу түзүлөт. Келгиле, биринчи бөлүмдөгү техникалык мүнөздөмөлөрдү эстеп көрөлү:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  ]
}
Бизде биринчи жолу талаалар жетиштүү, ошондуктан аны ишке ашыра баштайлы. Биринчи үч талаа суроолорду жаратпайт - бул кадимки саптар, бирок эмгек акы чөйрөсү буга чейин эле сунуш кылат. Эмне үчүн чыныгы линия? Чыныгы жумушта бул да болот, сизге кардар келип, - мен сизге бул пайдалуу жүктү жөнөткүм келет, сиз аны иштетесиз. Сиз, албетте, ийин куушуруп, кылсаңыз болот, бир пикирге келүүгө аракет кылып, маалыматтарды керектүү форматта берүү жакшыраак экенин түшүндүрсөңүз болот. Келгиле, биз акылдуу кардарды кезиктирдик жана сандарды сандык форматта берүү жакшыраак деп макулдаштык, ал эми биз акча жөнүндө сөз болуп жаткандыктан, Double болсун. Биздин пайдалуу жүктүн кийинки параметри жумушка кабыл алуу күнү болот, кардар аны бизге макулдашылган форматта жөнөтөт: жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж). 08-12. Учурдагы акыркы талаа кардарга берилген тапшырмалардын тизмеси болот. Албетте, Task - бул биздин маалымат базабыздагы дагы бир an object, бирок биз ал жөнүндө азырынча көп нерсе билбейбиз, андыктан биз мурда Кызматкер менен кылгандай эң негизги an objectти түзөбүз. Азыр биз болжолдой турган бир гана нерсе, бир кызматкерге бирден көп тапшырма берorши мүмкүн, ошондуктан биз "Бирден-көпкө" деген ыкманы, бирден көпкө катышын колдонобуз. Көбүрөөк айтканда, кызматкерлердин tableсындагы бир жазуу тапшырмалар tableсындагы бир нече жазууларга туура келиши мүмкүн . Мен дагы бир кызматкерди башкасынан так айырмалай алышыбыз үчүн, уникалдууNumber талаасы сыяктуу нерсени кошууну чечтим. Азыркы учурда биздин Кызматкерлер классы мындай көрүнөт:
@Entity
@Data
@Accessors(chain = true)
public class Employee {

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

    @NotBlank
    @Column(unique = true)
    private String uniqueNumber;
    private String firstName;
    private String lastName;
    private String department;
    private Double salary;
    private LocalDate hired;

    @OneToMany
    @JoinColumn(name = "employee_id")
    List<Task> tasks = new ArrayList<>();
}
Тапшырма an objectи үчүн төмөнкү класс түзүлгөн:
@Entity
@Data
@Accessors(chain = true)
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long taskId;
}
Мен айткандай, биз Тапшырмада жаңы эч нерсе көрбөйбүз, бул класс үчүн жаңы репозиторий да түзүлдү, ал Кызматкер үчүн репозиторийдин көчүрмөсү - мен аны бербейм, аны аналогия боюнча өзүңүз түзө аласыз. Бирок Кызматкерлер классы жөнүндө айтуунун мааниси бар. Мен айткандай, биз бир нече талааларды коштук, бирок азыр акыркысы гана кызыгуу жаратууда - тапшырмалар. Бул Тизме<Тапшырма> тапшырмалар , ал дароо бош ArrayList менен инициализацияланат жана бир нече annotationлар менен белгиленет. 1. @OneToMany Мен айткандай, бул биздин кызматкерлердин милдеттерге болгон катышы болот. 2. @JoinColumn - an objectтер кошула турган тилке. Бул учурда, биздин кызматкердин идентификаторун көрсөткөн Тапшырма tableсында кызматкер_идентификатор тилкеси түзүлөт; ал бизге ForeighnKey катары кызмат кылат Аттын көрүнгөн ыйыктыгына карабастан, тилкени каалагандай атай аласыз. Эгер сиз ID гана эмес, кандайдыр бир реалдуу мамычаны колдонушуңуз керек болсо, кырдаал бир аз татаалыраак болот; биз бул темага кийинчерээк токтолобуз. 3. Сиз ошондой эле id жогоруда жаңы annotationны байкаган чыгарсыз - @JsonIgnore. Идентификатор биздин ички an objectибиз болгондуктан, аны кардарга кайтарып берүүнүн кереги жок. 4. @NotBlank - бул текшерүү үчүн атайын annotation, анда талаа нөл болбошу керек же бош сап болбошу керек 5. @Column(unique = true) бул тилке уникалдуу маанилерге ээ болушу керектигин айтат. Ошентип, бизде буга чейин эки субъект бар, алар бири-бири менен байланышта. Аларды биздин программага интеграциялоонун мезгor келди – келгиле, сервистер жана контроллерлор менен иш алып баралы. Биринчиден, келгиле, getAllEmployees() методунан stub'ду алып салып, аны иш жүзүндө иштей турган нерсеге айландыралы:
public List<Employee> getAllEmployees() {
       return employeeRepository.findAll();
   }
Ошентип, биздин репозиторий маалымат базасынан болгон нерселердин баарын тырмап, бизге берет. Белгилей кетчү нерсе, ал ошондой эле тапшырмалардын тизмесин алат. Бирок аны тырмоо, албетте, кызыктуу, бирок ал жерде эч нерсе жок болсо, аны жок кылуу эмне? Туура, бул биз ал жерге бир нерсени кантип коюу керектигин аныкташыбыз керек дегенди билдирет. Биринчиден, контроллерубузга жаңы ыкманы жазалы.
@PostMapping("${application.endpoint.employee}")
    public ResponseEntity<?> createOrUpdateEmployee(@RequestBody final Employee employee) {
        try {
            employeeService.save(employee);
        } catch (ValidationException e) {
            return ResponseEntity.badRequest().body(e.getViolations());
        }
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
Бул @PostMapping, б.а. ал биздин кызматкерлердин акыркы чекитине келген POST суроо-талаптарын иштетет. Жалпысынан алганда, мен бул контроллерге бардык суроо-талаптар бир чекитке келгендиктен, муну бир аз жөнөкөйлөштүрөлү деп ойлогом. application.yml ичиндеги жакшы орнотууларыбызды эстейсизби? Келгиле, аларды оңдойлу. Колдонмо бөлүмү азыр мындай болсун:
application:
  endpoint:
    root: api/v1
    employee: ${application.endpoint.root}/employees
    task: ${application.endpoint.root}/tasks
Бул бизге эмне берет? Контроллерде биз ар бир конкреттүү ыкма үчүн картаны алып таштай ала тургандыгыбыз жана акыркы чекит класс деңгээлинде @RequestMapping ("${application.endpoint.employee}") annotationсында орнотулат . биздин контролер:
@RestController
@RequestMapping("${application.endpoint.employee}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }

    @PostMapping
    public ResponseEntity<?> createOrUpdateEmployee(@RequestBody final Employee employee) {
        try {
            employeeService.save(employee);
        } catch (ValidationException e) {
            return ResponseEntity.badRequest().body(e.getViolations());
        }
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
}
Бирок, келгиле, уланталы. CreateOrUpdateEmployee методунда так эмне болот? Албетте, биздин staffeService сактоо ыкмасына ээ, ал бардык үнөмдөө иштери үчүн жооп бериши керек. Бул ыкма өзүн-өзү түшүндүрүүчү аты менен өзгөчө ыргыта ала тургандыгы айдан ачык. Ошол. валидациянын кандайдыр бир түрү жүргүзүлүүдө. Ал эми жооп 201 Created же 400 badRequest болобу, эмне туура эмес болгонун тизмеси менен текшерүү натыйжаларына түздөн-түз көз каранды. Келечекте бул биздин жаңы валидация кызматыбыз, ал кирүүчү маалыматтарды талап кылынган талаалардын бар-жоктугун текшерет (@NotBlank эсиңиздеби?) жана мындай маалымат бизге ылайыктуубу же жокпу, чечет. Сактоо ыкмасына өтүүдөн мурун, келгиле, бул кызматты текшерип, ишке ашыралы. Бул үчүн мен өзүнчө валидация пакетин түзүүнү сунуштайм, ага биз өзүбүздүн кызматыбызды киргизебиз.
import com.example.demo.exception.ValidationException;
import com.example.demo.persistence.entity.Employee;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

@Service
@RequiredArgsConstructor
public class ValidationService {

    private final Validator validator;

    public boolean isValidEmployee(Employee employee) throws ValidationException {
        Set<Constraintviolation<Employee>> constraintViolations = validator.validate(employee);

        if (CollectionUtils.isNotEmpty(constraintViolations)) {
            throw new ValidationException(buildViolationsList(constraintViolations));
        }
        return true;
    }

    private <T> List<Violation> buildViolationsList(Set<Constraintviolation<T>> constraintViolations) {
        return constraintViolations.stream()
                                   .map(violation -> new Violation(
                                                   violation.getPropertyPath().toString(),
                                                   violation.getMessage()
                                           )
                                   )
                                   .toList();
    }
}
Класс өтө чоң болуп чыкты, бирок дүрбөлөңгө түшпөңүз, биз аны азыр аныктайбыз :) Бул жерде биз javax.validation даяр валидация китепканасынын куралдарын колдонобуз Бул китепкана бизге жаңы көз карандылыктан келген. build.graddle ишке ашырууга кошулду 'org.springframework.boot:spring-boot-starter -validation' Биздин эски досторубуз Сервис жана RequiredArgsConstructor Бизге бул класс жөнүндө бorшибиз керек болгон нерселердин бардыгын айтып берген, ошондой эле жеке акыркы валидатор талаасы бар. Ал сыйкыр кылат. Биз isValidEmployee ыкмасын түздүк, ага биз Кызматкер an objectин өткөрө алабыз; бул ыкма ValidationException ыргытат, аны биз бир аз кийинчерээк жазабыз. Ооба, бул биздин муктаждыктарыбыз үчүн өзгөчө өзгөчөлүк болот. validator.validate(кызматкер) ыкмасын колдонуу менен, биз ConstraintViolation an objectтеринин тизмесин алабыз - биз мурда кошкон валидация annotationлары менен дал келбеген нерселердин бардыгы. Андан аркы логика жөнөкөй, эгерде бул тизме бош болбосо (б.а. бузуулар бар), биз өзгөчөлүктү ыргытып, бузуулардын тизмесин түзөбүз - buildViolationsList методу Сураныч, бул Generic ыкмасы экенин эске алыңыз, б.а. ар кандай an objectилердин бузуулардын тизмелери менен иштей алат - эгерде биз башка нерсени текшерсек, келечекте пайдалуу болушу мүмкүн. Бул ыкма чынында эмне кылат? Агым API колдонуп, биз бузуулардын тизмесин карап чыгабыз. Карта методундагы ар бир бузууну өзүбүздүн бузуу an objectисине айландырып, пайда болгон бардык an objectтерди тизмеге чогултабыз. Биз аны кайтарып жатабыз. Биздин өзүбүздүн дагы эмнени бузуу an objectиси бар, сиз сурайсызбы? Бул жерде жөнөкөй рекорд
public record Violation(String property, String message) {}
Жазуулар - бул Javaдагы өзгөчө жаңылыктар, эгер сизге эч кандай логикасы жок же башка эч нерсеси жок, маалыматтары бар an object керек болсо. Бул эмне үчүн жасалганын мен өзүм түшүнө элек болсом да, кээде бул абдан ыңгайлуу нерсе. Ал кадимки класс сыяктуу өзүнчө файлда түзүлүшү керек. Ыңгайлаштырылган ValidationExceptionке кайтуу - бул төмөнкүдөй көрүнөт:
@RequiredArgsConstructor
public class ValidationException extends Exception {

    @Getter
    private final List<Violation> violations;
}
Ал бардык бузуулардын тизмесин сактайт, Lombok annotationсы - Getter тизмеге тиркелет жана башка Lombok annotationсы аркылуу биз керектүү конструкторду "ишке киргиздик" :) Бул жерде мен isValidтин жүрүм-турумун такыр туура эмес ишке ашырганымды белгилей кетүү керек. ... методу, ал чындыкты же өзгөчө жагдайды кайтарат, бирок кадимки False менен чектелип калмак. Өзгөчө мамиле жасалды, анткени мен бул катаны кардарга кайтаргым келет, бул логикалык ыкмадан чыныгы же жалгандан башка нерсени кайтарышым керек дегенди билдирет. Таза ички валидация ыкмалары болгон учурда, өзгөчөлүктү таштоонун кереги жок; бул жерде журналга жазуу талап кылынат. Бирок, келгиле, биздин EmployeeServiceге кайрылып көрөлү, биз дагы эле an objectтерди сактоону башташыбыз керек :) Келгиле, бул класс азыр кандай экенин карап көрөлү:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;
    private final ValidationService validationService;

    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @Transactional
    public void save(Employee employee) throws ValidationException {
        if (validationService.isValidEmployee(employee)) {
            Employee existingEmployee = employeeRepository.findByUniqueNumber(employee.getUniqueNumber());
            if (existingEmployee == null) {
                employeeRepository.save(employee);
            } else {
                existingEmployee = updateFields(existingEmployee, employee);
                employeeRepository.save(existingEmployee);
            }
        }
    }

    private Employee updateFields(Employee existingEmployee, Employee updatedEmployee) {
        return existingEmployee.setDepartment(updatedEmployee.getDepartment())
                               .setSalary(updatedEmployee.getSalary())
            				 //TODO implement tasks merging instead of replacement
                               .setTasks(updatedEmployee.getTasks());
    }
}
Жаңы акыркы касиетке көңүл буруңуз жеке акыркы ValidationService validationService; Сактоо ыкмасынын өзү @Transactional annotationсы менен белгиленет, андыктан RuntimeException алынса, өзгөртүүлөр артка кайтарылат. Биринчиден, биз жаңы эле жазган кызматтын жардамы менен келген маалыматтарды текшеребиз. Эгер баары ойдогудай болсо, биз маалымат базасында бар кызматкердин бар же жок экенин текшеребиз (уникалдуу номерди колдонуу менен). Болбосо, жаңысын сактайбыз, эгер бар болсо, класстагы талааларды жаңыртабыз. Ооба, чындыгында кантип текшеребиз? Ооба, бул абдан жөнөкөй, биз Кызматкерлердин репозиторийине жаңы ыкма коштук:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    Employee findByUniqueNumber(String uniqueNumber);
}
Эмнеси көрүнүктүү? Мен эч кандай логика же SQL сурам жазган жокмун, бирок бул жерде жеткorктүү. Жаз, жөн гана методдун атын окуу менен, мен эмнени каалаарын аныктайт - ByUniqueNumber таап, тиешелүү сапты методго өткөрүп бериңиз. Талааларды жаңыртууга кайтып келүү - бул жерде мен акыл-эсти колдонуп, бөлүмдү, эмгек акыны жана милдеттерди гана жаңыртууну чечтим, анткени атын өзгөртүү, алгылыктуу нерсе болсо да, дагы деле кеңири таралган эмес. Ал эми жумушка алуу күнүн өзгөртүү негизинен талаштуу маселе. Бул жерде эмне кылуу жакшы болмок? Тапшырма тизмелерин бириктириңиз, бирок бизде азырынча тапшырмалар жок болгондуктан жана аларды кантип айырмалоону билбейбиз, биз TODOдон чыгабыз. Биздин Франкенштейнди ишке киргизгенге аракет кылалы. Эгерде мен эч нерсени сүрөттөөнү унутпасам, анда ал иштеши керек, бирок биринчиден, бул жерде биз алган класс дарагы: REST API жана маалыматтарды текшерүү - 1 Өзгөрүлгөн класстар көк түс менен, жаңылары жашыл түс менен белгиленет, эгерде сиз иштесеңиз, мындай көрсөткүчтөрдү алууга болот. git репозиторий менен, бирок git биздин макаланын темасы эмес, андыктан биз ага токтолбойбуз. Ошентип, учурда бизде GET жана POST эки ыкмасын колдогон бир акыркы чекит бар. Баса, акыркы чекит жөнүндө кээ бир кызыктуу маалыматтар. Эмне үчүн, мисалы, биз GET жана POST үчүн getAllEmployees же createEmployees сыяктуу өзүнчө акыркы чекиттерди бөлгөн жокпуз? Баары өтө жөнөкөй - бардык суроо-талаптар үчүн бир пунктка ээ болуу алда канча ыңгайлуу. Маршрутизация HTTP методунун негизинде ишке ашат жана ал интуитивдик, getAllEmployees, getEmployeeByName, алуу... жаңыртуу... түзүү... жок кылуу... Колубузда болгон нерсени сынап көрөлү. Мен мурунку макалада бизге Почтачы керек деп жазганмын жана аны орнотууга убакыт келди. Программанын интерфейсинде биз жаңы POST сурамын түзүп REST API жана маалыматтарды текшерүү - 2 , аны жөнөтүүгө аракет кылабыз. Эгер баары ойдогудай болсо, экрандын оң жагында Status 201 алабыз. Бирок, мисалы, бир эле нерсени жөнөтүп, бирок уникалдуу номери жок (бизде валидация бар), мен башка жооп алдым: Келгиле REST API жана маалыматтарды текшерүү - 3 , биздин толук тандоо кандай иштээрин текшерип көрөлү - биз ошол эле акыркы чекит үчүн GET ыкмасын түзүп, аны жөнөтөбүз. . REST API жана маалыматтарды текшерүү - 4 Баары мен үчүн болгондой эле, сиз үчүн да ийгorктүү болду деп чын жүрөктөн үмүттөнөм жана кийинки макалада көрүшкөнчө .
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION