Java REST di HeadHunter School of Programmer

Hai Habr, kami ingin berbicara tentang salah satu proyek SchoolH Programmer HeadHunter 2018. Di bawah ini adalah artikel dari lulusan kami di mana ia akan berbicara tentang pengalaman yang diperoleh selama pelatihan.



Halo semuanya. Tahun ini saya lulus dari Sekolah Tinggi Programer dan di pos ini saya akan berbicara tentang proyek pelatihan di mana saya berpartisipasi. Selama pelatihan di sekolah, dan terutama pada proyek, saya tidak memiliki contoh aplikasi tempur (dan bahkan panduan yang lebih baik), di mana saya bisa melihat bagaimana cara memisahkan logika dengan benar dan membangun arsitektur yang dapat diukur. Semua artikel yang saya temukan sulit dipahami oleh pemula baik IoC secara aktif digunakan di dalamnya tanpa penjelasan yang komprehensif tentang cara menambahkan komponen baru atau memodifikasi yang lama, atau mereka kuno dan berisi satu ton konfigurasi xml dan antarmuka jsp. Saya mencoba untuk fokus pada level saya sebelum pelatihan, yaitu hampir nol dengan beberapa peringatan, jadi artikel ini harus bermanfaat bagi siswa masa depan sekolah, serta penggemar otodidak yang memutuskan untuk mulai menulis di Jawa.


Diberikan (pernyataan masalah)


Tim - 5 orang. Jangka waktu 3 bulan, di setiap akhir ada demo. Tujuannya adalah untuk membuat aplikasi yang membantu SDM menemani karyawan pada masa percobaan, mengotomatiskan semua proses yang ternyata. Di pintu masuk, kami diberitahu bagaimana masa percobaan (IP) sekarang diatur: segera setelah diketahui bahwa seorang karyawan baru akan keluar, HR mulai menendang pemimpin masa depan untuk mengatur tugas untuk IP, dan ini perlu dilakukan sebelum hari kerja pertama. Pada hari karyawan pergi bekerja, HR mengadakan pertemuan penyambutan, berbicara tentang infrastruktur perusahaan dan menyerahkan tugas untuk IP. Setelah 1,5 dan 3 bulan, pertemuan antara dan terakhir HR, pemimpin dan karyawan diadakan, di mana keberhasilan bagian dibahas dan bentuk hasil disusun. Jika berhasil, setelah pertemuan terakhir, karyawan tersebut menyerahkan kuesioner yang dicetak untuk pemula (pertanyaan dengan gaya "nikmati kesenangan IP") dan dapatkan tugas SDM agar jira dapat diterbitkan kepada karyawan VHI.


Desain


Kami memutuskan untuk membuat bagi setiap karyawan sebuah halaman pribadi di mana informasi umum akan ditampilkan (nama, departemen, pemimpin, dll.), Bidang untuk komentar dan perubahan riwayat, file yang dilampirkan (tugas pada IP, kuesioner) dan alur kerja karyawan yang mencerminkan tingkat berlalunya IP. Alur kerja diputuskan untuk dibagi menjadi 8 tahap, yaitu:


  • Tahap 1 - menambah karyawan: segera diselesaikan setelah mendaftarkan karyawan baru dalam sistem SDM. Pada saat yang sama, tiga kalender dikirim ke HR untuk sumur, pertemuan antara dan terakhir.
  • Tahap 2 - koordinasi tugas pada IP: formulir dikirim ke kepala untuk menetapkan tugas pada IP, yang akan diterima SDM setelah diisi. Selanjutnya, HR mencetaknya, menandatanganinya dan menandai penyelesaian tahap di antarmuka.
  • Tahap 3 - sambutan-pertemuan: HR mengadakan rapat dan menekan tombol "Tahap selesai".
  • Tahap 4 - pertemuan sementara: mirip dengan tahap ketiga
  • Tahap 5 - hasil pertemuan sementara: SDM mengisi hasil pada halaman karyawan dan mengklik "Berikutnya".
  • Tahap 6 - pertemuan terakhir: mirip dengan tahap ketiga
  • Tahap 7 - hasil dari pertemuan terakhir: mirip dengan tahap kelima
  • Tahap 8 - penyelesaian IP: jika berhasil menyelesaikan IP, karyawan akan dikirim tautan dengan bentuk kuesioner melalui email, dan dalam jira tugas untuk pendaftaran asuransi kesehatan sukarela akan secara otomatis dibuat (kami mendapat tugas dengan tangan).

Semua tahap memiliki waktu, setelah itu tahap dianggap berakhir dan disorot dengan warna merah, dan pemberitahuan tiba melalui pos. Waktu akhir harus dapat diedit, misalnya, jika pertemuan sementara jatuh pada hari libur umum atau karena alasan tertentu rapat harus dijadwalkan ulang.
Sayangnya, prototipe yang digambar di selembar kertas / papan belum dilestarikan, tetapi pada akhirnya akan ada screenshot dari aplikasi yang sudah selesai.


Operasi


Salah satu tujuan sekolah adalah untuk mempersiapkan siswa untuk bekerja di proyek-proyek besar, sehingga proses melepaskan tugas sesuai untuk kita.
Di akhir pekerjaan pada tugas, kami memberikannya untuk ulasan_1 kepada siswa lain dari tim untuk memperbaiki kesalahan yang jelas / bertukar pengalaman. Kemudian datang ulasan_2 - tugas diperiksa oleh dua mentor yang memastikan bahwa kami tidak melepaskan govnokod berpasangan dengan reviewer_1. Tes lebih lanjut seharusnya, tetapi tahap ini tidak terlalu tepat, mengingat skala proyek sekolah. Jadi setelah melalui tinjauan, kami berpikir bahwa tugas itu siap untuk dirilis.
Sekarang beberapa kata tentang penyebaran. Aplikasi harus tersedia sepanjang waktu di jaringan dari komputer mana pun. Untuk melakukan ini, kami membeli mesin virtual murah (untuk 100 rubel / bulan), tetapi, seperti yang saya ketahui kemudian, semuanya dapat diatur secara gratis dan dengan cara yang modis di buruh pelabuhan AWS . Untuk integrasi berkelanjutan, kami memilih Travis. Jika ada yang tidak tahu (saya pribadi belum pernah mendengar tentang integrasi terus-menerus sebelum sekolah), ini adalah hal yang sangat keren yang akan diawasi github Anda dan ketika komit baru muncul (cara mengkonfigurasinya), kumpulkan kode dalam toples, kirimkan ke toples, kirimkan ke server dan mulai ulang aplikasi secara otomatis. Bagaimana membangunnya dijelaskan dalam Jam Travis di root proyek, ini sangat mirip dengan bash, jadi saya pikir tidak ada komentar yang diperlukan. Kami juga membeli domain www.adaptation.host agar tidak mendaftarkan alamat IP yang jelek di bilah alamat di demo. Kami juga mengonfigurasi postfix (untuk mengirim surat), apache (bukan nginx, karena apache di luar kotak) dan server jira (percobaan). Frontend dan backend dibuat oleh dua layanan terpisah yang akan berkomunikasi melalui http (# 2k18, # microservices). Ini bagian dari artikel "di HeadHunter School of Programmer" dengan lancar berakhir, dan kami beralih ke layanan java.


Backend


0. Pendahuluan


Kami menggunakan teknologi berikut:


  • JDK 1.8;
  • Maven 3.5.2;
  • Postgres 9.6;
  • Hibernasi 5.2.10;
  • Dermaga 9.4.8;
  • Jersey 2.27.

Sebagai kerangka kerja, kami mengambil NaB 3.5.0 dari jam. Pertama, digunakan di HeadHunter, dan kedua, berisi jetty, jersey, hibernate, postgres yang disematkan di luar kotak, yang ditulis di github. Saya akan menjelaskan secara singkat untuk pemula: jetty adalah server web yang mengidentifikasi klien dan mengatur sesi untuk masing-masing dari mereka; jersey - kerangka kerja yang membantu untuk dengan mudah membuat layanan RESTful; hibernate - ORM untuk menyederhanakan bekerja dengan database; maven adalah kolektor proyek java.
Saya akan menunjukkan contoh sederhana bagaimana bekerja dengan ini. Saya membuat repositori pengujian kecil, di mana saya menambahkan dua entitas: pengguna dan resume, serta sumber daya untuk membuat dan menerimanya dengan tautan OneToMany / ManyToOne. Untuk memulai, cukup tiruan repositori dan jalankan mvn clean install exec: java di root proyek. Sebelum mengomentari kode, saya akan memberi tahu Anda tentang struktur layanan kami. Itu terlihat seperti ini:



Direktori utama:


  • Layanan - direktori utama dalam aplikasi, semua logika bisnis disimpan di sini. Di tempat lain, bekerja dengan data tanpa alasan yang baik seharusnya tidak dilakukan.
  • Sumberdaya - penangan url, lapisan antara layanan dan frontend. Validasi data masuk dan konversi data keluar, tetapi bukan logika bisnis, diizinkan di sini.
  • Dao (Obyek Akses Data) - lapisan antara database dan layanan. Tao hanya boleh berisi operasi dasar yang mendasar: tambahkan, hitung, perbarui, hapus satu / semua.
  • Entity - objek yang dipertukarkan ORM dengan database. Sebagai aturan, mereka langsung berhubungan dengan tabel dan harus berisi semua bidang sebagai entitas dalam database dengan tipe yang sesuai.
  • Dto (Data Transfer Object) - analog entitas, hanya untuk sumber daya (depan), membantu membentuk json dari data yang ingin kami kirim / terima.

1. Basis


Dengan cara yang baik, Anda harus menggunakan postgres yang terinstal di dekatnya, seperti pada aplikasi utama, tetapi saya ingin test case sederhana dan dijalankan dengan satu perintah, jadi saya mengambil HSQLDB bawaan. Menghubungkan database ke infrastruktur kami dilakukan dengan menambahkan DataSource ke ProdConfig (juga ingat untuk memberi tahu hibernate database mana yang Anda gunakan):


@Bean(destroyMethod = "shutdown") DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("db/sql/create-db.sql") .build(); } 

Saya membuat skrip pembuatan tabel dalam file create-db.sql. Anda dapat menambahkan skrip lain yang menginisialisasi database. Dalam contoh in_mory yang ringan, kita dapat melakukannya tanpa skrip sama sekali. Jika Anda menentukan hibernate.hbm2ddl.auto=create di pengaturan hibernate.properties, maka hibernate itu sendiri akan membuat tabel dengan entitas saat aplikasi dimulai. Tetapi jika Anda perlu memiliki sesuatu dalam database yang tidak dimiliki entitas, maka Anda tidak dapat melakukannya tanpa file. Secara pribadi, saya terbiasa berbagi database dan aplikasi, jadi saya biasanya tidak percaya hibernate untuk melakukan hal-hal seperti itu.
db/sql/create-db.sql :


 CREATE TABLE employee ( id INTEGER IDENTITY PRIMARY KEY, first_name VARCHAR(256) NOT NULL, last_name VARCHAR(256) NOT NULL, email VARCHAR(128) NOT NULL ); CREATE TABLE resume ( id INTEGER IDENTITY PRIMARY KEY, employee_id INTEGER NOT NULL, position VARCHAR(128) NOT NULL, about VARCHAR(256) NOT NULL, FOREIGN KEY (employee_id) REFERENCES employee(id) ); 

2. Entitas


entities/employee :


 @Entity @Table(name = "employee") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Integer id; @Column(name = "first_name", nullable = false) private String firstName; @Column(name = "last_name", nullable = false) private String lastName; @Column(name = "email", nullable = false) private String email; @OneToMany(mappedBy = "employee") @OrderBy("id") private List<Resume> resumes; //..geters and seters.. } 

entities/resume :


 @Entity @Table(name = "resume") public class Resume { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "employee_id") private Employee employee; @Column(name = "position", nullable = false) private String position; @Column(name = "about") private String about; //..geters and seters.. } 

Entitas tidak merujuk satu sama lain dengan bidang kelas, tetapi dengan seluruh objek induk / anak. Dengan demikian, kita bisa mendapatkan rekursi ketika kita mencoba mengambil dari database Karyawan, untuk resume yang diambil, untuk yang ... Untuk mencegah hal ini terjadi, kami menunjukkan anotasi @OneToMany(mappedBy = "employee") dan @ManyToOne(fetch = FetchType.LAZY) . Mereka akan diperhitungkan dalam layanan ketika melakukan transaksi tulis / baca dari database. Menyiapkan FetchType.LAZY adalah opsional, tetapi menggunakan komunikasi malas membuat transaksi lebih mudah. Jadi, jika dalam transaksi kita mendapatkan resume dari database dan tidak menghubungi pemiliknya, maka entitas karyawan tidak akan dimuat. Anda dapat memverifikasi ini sendiri: hapus FetchType.LAZY dan lihat di debug itu kembali dari layanan bersama dengan resume. Tetapi Anda harus berhati-hati - jika kami tidak memuat karyawan dalam transaksi, maka mengakses bidang karyawan di luar transaksi dapat menyebabkan LazyInitializationException .


3. Dao


Dalam kasus kami, EmployeeDao dan ResumeDao hampir identik, jadi saya akan memberi di sini hanya satu saja
EmployeeDao :


 public class EmployeeDao { private final SessionFactory sessionFactory; @Inject public EmployeeDao(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public void save(Employee employee) { sessionFactory.getCurrentSession().save(employee); } public Employee getById(Integer id) { return sessionFactory.getCurrentSession().get(Employee.class, id); } } 

@Inject berarti bahwa dalam konstruktor dao kami, Dependency Injection digunakan. Dalam kehidupan masa lalu saya, seorang fisikawan yang membagi-bagikan file, membuat grafik berdasarkan hasil angka dan, setidaknya, menemukan OOP, dalam panduan java, konstruksi seperti itu tampak agak gila. Dan di sekolah, mungkin, topik ini adalah yang paling tidak jelas, IMHO. Untungnya, ada banyak materi tentang DI di Internet. Jika Anda terlalu malas untuk membaca, maka bulan pertama Anda dapat mengikuti aturan: daftarkan sumber daya / layanan baru / Tao dalam konteks-konfigurasi kami , tambahkan entitas ke pemetaan . Jika Anda perlu menggunakan beberapa layanan / tao di layanan lain, Anda harus menambahkannya di konstruktor dengan suntikan anotasi, seperti yang ditunjukkan di atas, dan pegas menginisialisasi segalanya untuk Anda. Tetapi Anda masih harus berurusan dengan DI.


4. Dto


Dto, seperti dao, hampir identik untuk karyawan dan resume. Kami menganggap hanya karyawan yang datang ke sini. Kami akan membutuhkan dua kelas: EmployeeCreateDto , diperlukan saat membuat karyawan; EmployeeDto digunakan pada tanda terima (berisi id bidang tambahan dan resumes ). Bidang id ditambahkan sehingga di masa mendatang, atas permintaan dari luar, kami dapat bekerja dengan karyawan tanpa melakukan pencarian awal entitas melalui email . Bidang resumes untuk menerima karyawan beserta semua resume dalam satu permintaan. Dimungkinkan untuk mengelola dengan satu dto untuk semua operasi, tetapi kemudian untuk daftar semua resume karyawan tertentu kita harus membuat sumber daya tambahan, seperti getResumesByEmployeeEmail, mencemari kode dengan kueri basis data khusus dan mencoret semua fasilitas yang disediakan oleh ORM.
EmployeeCreateDto :


 public class EmployeeCreateDto { public String firstName; public String lastName; public String email; } 

EmployeeDto :


 public class EmployeeDto { public Integer id; public String firstName; public String lastName; public String email; public List<ResumeDto> resumes; public EmployeeDto(){ } public EmployeeDto(Employee employee){ id = employee.getId(); firstName = employee.getFirstName(); lastName = employee.getLastName(); email = employee.getEmail(); if (employee.getResumes() != null) { resumes = employee.getResumes().stream().map(ResumeDto::new).collect(Collectors.toList()); } } } 

Sekali lagi, saya menarik perhatian pada fakta bahwa menulis logika di dto sangat tidak senonoh sehingga semua bidang ditetapkan sebagai public , agar tidak menggunakan getter dan setter.


5. Layanan


EmployeeService :


 public class EmployeeService { private EmployeeDao employeeDao; private ResumeDao resumeDao; @Inject public EmployeeService(EmployeeDao employeeDao, ResumeDao resumeDao) { this.employeeDao = employeeDao; this.resumeDao = resumeDao; } @Transactional public EmployeeDto createEmployee(EmployeeCreateDto employeeCreateDto) { Employee employee = new Employee(); employee.setFirstName(employeeCreateDto.firstName); employee.setLastName(employeeCreateDto.lastName); employee.setEmail(employeeCreateDto.email); employeeDao.save(employee); return new EmployeeDto(employee); } @Transactional public ResumeDto createResume(ResumeCreateDto resumeCreateDto) { Resume resume = new Resume(); resume.setEmployee(employeeDao.getById(resumeCreateDto.employeeId)); resume.setPosition(resumeCreateDto.position); resume.setAbout(resumeCreateDto.about); resumeDao.save(resume); return new ResumeDto(resume); } @Transactional(readOnly = true) public EmployeeDto getEmployeeById(Integer id) { return new EmployeeDto(employeeDao.getById(id)); } @Transactional(readOnly = true) public ResumeDto getResumeById(Integer id) { return new ResumeDto(resumeDao.getById(id)); } } 

Transaksi tersebut yang melindungi kami dari LazyInitializationException (dan tidak hanya). Untuk memahami transaksi dalam hibernasi, saya merekomendasikan kerja yang sangat baik pada hub ( baca selengkapnya ... ), yang banyak membantu saya pada waktunya.


6. Sumberdaya


Terakhir, tambahkan sumber daya untuk membuat dan mendapatkan entitas kami:
EmployeeResource :


 @Path("/") @Singleton public class EmployeeResource { private final EmployeeService employeeService; public EmployeeResource(EmployeeService employeeService) { this.employeeService = employeeService; } @GET @Produces("application/json") @Path("/employee/{id}") @ResponseBody public Response getEmployee(@PathParam("id") Integer id) { return Response.status(Response.Status.OK) .entity(employeeService.getEmployeeById(id)) .build(); } @POST @Produces("application/json") @Path("/employee/create") @ResponseBody public Response createEmployee(@RequestBody EmployeeCreateDto employeeCreateDto){ return Response.status(Response.Status.OK) .entity(employeeService.createEmployee(employeeCreateDto)) .build(); } @GET @Produces("application/json") @Path("/resume/{id}") @ResponseBody public Response getResume(@PathParam("id") Integer id) { return Response.status(Response.Status.OK) .entity(employeeService.getResumeById(id)) .build(); } @POST @Produces("application/json") @Path("/resume/create") @ResponseBody public Response createResume(@RequestBody ResumeCreateDto resumeCreateDto){ return Response.status(Response.Status.OK) .entity(employeeService.createResume(resumeCreateDto)) .build(); } } 

Produces(“application/json”) diperlukan agar json dan dto dikonversi dengan benar satu sama lain. Ini membutuhkan ketergantungan pom.xml:


 <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>${jersey.version}</version> </dependency> 

Konverter json lain karena alasan tertentu mengekspos MediaType yang tidak valid.


7. Hasil


Jalankan dan periksa apa yang kita miliki ( mvn clean install exec:java di root proyek). Port tempat aplikasi berjalan ditentukan dalam service.properties . Buat pengguna dan lanjutkan. Saya melakukan ini dengan ikal, tetapi Anda dapat menggunakan tukang pos jika Anda membenci konsol.


 curl --header "Content-Type: application/json" \ --request POST \ --data '{"firstName": "Jason", "lastName": "Statham", "email": "jasonst@t.ham"}' \ http://localhost:9999/employee/create curl --header "Content-Type: application/json" \ --request POST \ --data '{"employeeId": 0, "position": "Voditel", "about": "Opyt raboty perevozchikom 15 let"}' \ http://localhost:9999/resume/create curl --header "Content-Type: application/json" --request GET http://localhost:9999/employee/0 curl --header "Content-Type: application/json" --request GET http://localhost:9999/employee/0 

Semuanya bekerja dengan baik. Jadi kami mendapat backend yang menyediakan api. Sekarang Anda dapat memulai layanan dengan frontend dan menggambar formulir yang sesuai. Ini adalah fondasi yang baik untuk aplikasi yang dapat Anda gunakan untuk memulai sendiri dengan mengkonfigurasi berbagai komponen saat proyek berkembang.


Kesimpulan


Kode aplikasi utama tetap berfungsi pada github dengan instruksi untuk memulai di tab wiki. Screenshot yang dijanjikan:




Untuk proyek multi-juta dolar, tentu saja terlihat agak lembab, tetapi sebagai alasan, saya mengingatkan Anda bahwa kami mengerjakannya pada malam hari, setelah bekerja / belajar.
Jika jumlah orang yang tertarik melebihi jumlah sandal, di masa depan saya dapat mengubahnya menjadi serangkaian artikel di mana saya akan berbicara tentang bagian depan, buruh pelabuhan dan nuansa yang kami temui ketika bekerja dengan file mail / fat / dock.


PS Setelah beberapa waktu selamat dari kejutan dari sekolah, anggota tim lainnya berkumpul dan, setelah menganalisis penerbangan, memutuskan untuk membuat adaptasi 2.0, dengan mempertimbangkan semua kesalahan. Tujuan utama dari proyek ini adalah sama - untuk mempelajari cara membuat aplikasi serius, membangun arsitektur yang dipikirkan secara matang dan diminati oleh para spesialis di pasar. Anda dapat mengikuti pekerjaan di repositori yang sama. Permintaan kolam renang dipersilakan. Terima kasih atas perhatian Anda dan semoga sukses!


roti


kuliah video hoc ioc

Source: https://habr.com/ru/post/id419599/


All Articles