You are on page 1of 119

Upoređivanje performansi i potrošnje

resursa između blocking i non-


blocking implementacija Java web
servera
Mentor: Kandidat:
Dr Danijela Boberić Krstićev Nikola Stevanović 412m/16
Motivacija

Efikasnost rada Java web servera

Performanse + potrošnja resursa

Produktivnost programera

Rang:

Vert.X #4, #8

Spring #59

Dropwizard #201

2
1. UVOD

3
UVOD
1. Konkurentno programiranje
2. Blocking i non-blocking implementacije
3. Sinhroni i asinhroni model programiranja
4. Reaktivno programiranje

4
Konkurentno programiranje

Tip programiranja koji vrši simultanu obradu zahteva

Osnovne jedinice izvršavanja:

Procesi (eng. Processes)

Niti (eng. Threads)

Višenitno okruženje (eng. Multithreaded enviorment)

synchronized, Lock objekti, volatile → happens-before veza

Mutual exclusion, deadlock, resource starvation

5
Blocking i non-blocking implementacije 1/2

BLOCKING NON-BLOCKING

Kontrola toka izvršavanja Kontrola toka izvršavanja


se vraća nakon izvršene se odmah vraća pri pozivu
korutine korutine → OS obaveštava
o završetku

Read/write; Read/write
select/poll → epoll (O_NONBLOCK);
aio_read/aio_write
Streams (samo napred) Selector, Channels +
Buffers (napred/nazad)

6
Blocking i non-blocking implementacije 2/2

7
Sinhroni i asinhroni modeli programiranja 1/2
Sinhroni model Asinhroni model

Koristi više niti za izvršavanje Koristi jednu nit za izvršavanje


taksova→čeka (uglavnom) da se taskova → OS obaveštava
rezultat vrati da program nastavi putem sistemskog poziva o
sa radom završenoj operaciji
Potencijalni problemi: Potencijalni problemi: callback
deadlock, resource starvation, hell, naporan proces debbuging-
data inconsistency a

Rešenja za potencijalne Rešenja za potencijalne


probleme: upotreba probleme: Promise/Future,
synchronized, Lock objekata, then()/compose(), logovanje...
java.uti.concurrent, volatile,...

Sistemski pozivi: Sistemski pozivi:


read/write, read/write (O_NONBLOCK),
select/poll → epoll aio_read / aio_write, AIO

8
Sinhroni i asinhroni modeli programiranja 2/2

9
Reaktivno programiranje 1/5

10
Reaktivno programiranje 2/5

Elastic (srp. Elastičnost) – brzo uzvraćanje odgovora pod velikim opterećenjem, amortizacija
resursa na osnovu stope input-a

Responsive (srp. Brz odziv) – brzo otkrivanje problema, efektivno rešavanje istih bez zastoja →
konzistentan kvalitet opsluživanja end user-a

Resilient (srp. Otpornost) – nastavlja sa radom usled kvara → postiže se:

Replikacijom – izvršavanje komponente na različitim lokacijama: Thread Pools, procesi, nodes,
itd. → skalabilnost

Izolacijom – loose coupling u vremenu i prostoru (primalac i pošiljalac su u različitim procesima)

Delegiranjem – „pređi moja muko na drugog“

Message Driven (srp. zasnovani na razmeni poruka) – asinhrona razmena poruka → event-ovi
(HTTP requests, vremenski događaji, OS signali, itd.) ne moraju biti razmenjeni u asinhronom stilu
(kratak vek trajanja)!

11
Reaktivno programiranje 3/5
Reaktivno programiranje se zasniva na:

Asinhronom modelu programiranja (callback funkcije)

Deklarativnoj paradigmi (map, filter, fold itd.)

Asinhroni tokovi podataka (eng. Asynchronous/Reactive Data Streams)

Non-blocking implementacije koda

Biblioteke: RxJava(2/3), RxJS, Rx.NET, RxSwift, RxScala, …

Potpomaže pisanju reaktivnih sistema → nije obavezno koristiti

12
Reaktivno programiranje 4/5

Reactor obrazac

Event Loop nit

13
Reaktivno programiranje 5/5

Multi-Reactor obrazac

Event Loop niti

14
Informacione tehnologije

Java 8

Eclipse

IntelIJ IDEA

Apache JMeter

VisualVM

JMX

15
2. IMPLEMENTACIJE
FRAMEWORK-a i BIBLIOTEKA

16
Implementacija framework-a i biblioteka

Java Standalone Web aplikacija koja koristi RESTful web
servise

SpringBoot i Dropwizard koriste MVC obrazac

Vert.X ~ Node.js

Uniformna šema Postgres baze podataka

17
18
SpringBoot aplikacija

19
Implementacija SpringBoot aplikacije 1/11
Upotrebljene biblioteke i tehnike:

MapStruct – mapiranje model klasa na dto klase i obrnuto

Project Lombok – anotacije za uklanjanje boiler-plate koda

Hibernate (GenerationType.SEQUNECE → HiLo algoritam) + JPA
specifikacija

Dependency Injection – tehnika za „ubrizgavanje“ podataka → @Autowired

20
Implementacija SpringBoot aplikacije 2/11

21
Implementacija SpringBoot aplikacije 3/11
//...
package com.nikolas.master_thesis.model; @ManyToMany(fetch = FetchType.LAZY, cascade =
// import-ovanje klasa i interfejsa neophodnih za rad {CascadeType.PERSIST, CascadeType.MERGE})
@Entity
@Getter @JoinTable(name = "category_book", joinColumns = {
@Setter @JoinColumn(name ="book_id")},
@NoArgsConstructor inverseJoinColumns = {
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @JoinColumn(name = "category_id")})
@ToString @OrderBy("categoryId ASC")
@Table(name = "book")
private Set < Category > categories;
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, @OneToMany(mappedBy = "book",
generator = "book_generator") cascade ={ CascadeType.PERSIST,
@SequenceGenerator(name = "book_generator", sequenceName = "book_seq", CascadeType.MERGE})
allocationSize = 1) @OrderBy("orderItemId ASC")
@EqualsAndHashCode.Include
private Set < OrderItem > orderItems;
private Long bookId;
private String title;
private double price; public Book(Long bookId, String title, double price, int amount,
private int amount; boolean isDeleted) {
private boolean isDeleted; this.bookId = bookId;
this.title = title;
@ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST,
this.price = price;
CascadeType.MERGE})
@JoinTable(name = "author_book", joinColumns = { this.amount = amount;
@JoinColumn(name = "book_id")}, inverseJoinColumns = { this.isDeleted = isDeleted;
@JoinColumn(name = "author_id")}) }
@OrderBy("authorId ASC") }
private Set<Author> authors;

22
Implementacija SpringBoot aplikacije 4/11

package com.nikolas.master_thesis.repository;
// import-ovanje neophodnih klasa za rad

@Repository
@Repository

JpaRepository public interface BookRepository extends JpaRepository<Book, Long>{

@Query("SELECT b FROM Book b JOIN FETCH b.authors a JOIN FETCH b.categories c WHERE "

@Query + "b.bookId = :id ORDER BY b.bookId ASC")
public Book getSingleBookById(@Param("id") Long bookId);

@Param @Query("SELECT DISTINCT b FROM Book b JOIN FETCH b.authors a JOIN FETCH b.categories c "
+ "ORDER BY b.bookId ASC")
public List<Book> getAllBooks();

@Query("SELECT DISTINCT b FROM Book b JOIN FETCH b.authors a JOIN FETCH b.categories c "

+ "WHERE b.bookId IN :bookIds ORDER BY b.bookId ASC")


public List<Book> getAllBooksFromOrder(@Param("bookIds") List<Long> bookIds);
}

23
Implementacija SpringBoot aplikacije 5/11
//...

@Service @Override
public BookListDTO getAllBooks() {

@Transactional BookListDTO bookListDTO = new BookListDTO();
List<Book> books = bookRepository.getAllBooks();
if (books != null && !books.isEmpty()) {

@Autowired for (Book book: books) {
bookListDTO.getBookListDTO()
.add(bookMapper.mapBookToBookDTO(book));
}
return bookListDTO;
package com.nikolas.master_thesis.serviceImpl; } else {
// import-ovanje neophodnih klasa za rad throw new StoreException("Exception, no books at all!",
HttpStatus.NOT_FOUND);
@Service }
@Transactional }
}
public class BookServiceImpl implements BookService
{
@Autowired
AuthorRepository authorRepository;
@Autowired
BookRepository bookRepository;
@Autowired
CategoryRepository categoryRepository; @Override
public Boolean deleteAuthor(Long authorId) {
@Autowired authorRepository.delete(author);
BookMapper bookMapper; return true;
}
//...

24
Implementacija SpringBoot aplikacije 6/11
@Override
public boolean updateBook(AddUpdateBookDTO addUpdateBookDTO, long
bookId) { Set<Category> categoriesToBeSaved = categoryRepository
if (addUpdateBookDTO == null) { .getCategoriesByCategoryIds(
throw new StoreException("Error Request body for book is empty!", addUpdateBookDTO.getCategoryIds());
HttpStatus.BAD_REQUEST); Set<Category> categoriesToBeDeleted = new HashSet<>();
} book.getCategories().addAll(categoriesToBeSaved);
Book book = bookRepository.getSingleBookById(bookId); for (Category category: book.getCategories()) {
if (book != null) { if (!categoriesToBeSaved.contains(category)) {
book.setTitle(addUpdateBookDTO.getTitle()); categoriesToBeDeleted.add(category);
// … }
book.setDeleted(addUpdateBookDTO.isDeleted()); }
book.getCategories().removeAll(categoriesToBeDeleted);
Set<Author> authorsToBeSaved = authorRepository bookRepository.save(book);
.getAuthorsByAuthorIds(addUpdateBookDTO.getAuthorIds()); return true;
Set<Author> authorsToBeDeleted = new HashSet<>(); } else {
book.getAuthors().addAll(authorsToBeSaved); throw new StoreException("Book for requested id = " + bookId
for (Author author: book.getAuthors()) { + " doesn't exist in database!", HttpStatus.NOT_FOUND);
if (!authorsToBeSaved.contains(author)) { }
authorsToBeDeleted.add(author); }
}
}
book.getAuthors().removeAll(authorsToBeDeleted);

25
Implementacija SpringBoot aplikacije 7/11

@ControllerAdvice
@ExceptionHandler(EntityNotFoundException.class)

@ExceptionHandler public ResponseEntity<?> handleEntityNotFoundException(
EntityNotFoundException ex, WebRequest request) {
LOGGER.error("Error, exeception "
package com.nikolas.master_thesis.exception; + ex.getClass().getSimpleName()
+ " occured! Cause: \n");
@ControllerAdvice ex.printStackTrace();
public class ExceptionsHandler extends ResponseEntityExceptionHandler { return new ResponseEntity<>(ex.getMessage(),
HttpStatus.NOT_FOUND);
private static final Logger LOGGER = LoggerFactory }
.getLogger(ExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleAnyException(Exception ex,
@ExceptionHandler(StoreException.class)
WebRequest request) {
public ResponseEntity<?> handleApiException(StoreException ex, LOGGER.error("Error, exeception " + ex.getClass().getSimpleName()
WebRequest request) { + " occured! Cause:\n");
LOGGER.error("Error, exeception " ex.printStackTrace();
+ ex.getClass().getSimpleName() + " occured! Cause: \n"); return new ResponseEntity<>(ex.getMessage(),
ex.printStackTrace();
return new ResponseEntity<>(ex.getMessage(), ex.getHttpStatus()); HttpStatus.INTERNAL_SERVER_ERROR);
} }
}

26
Implementacija SpringBoot aplikacije 8/11
//...
public BookDTO(Long bookId, String title, double price,
package com.nikolas.master_thesis.dto; int amount, boolean isDeleted) {
// import-ovanje klasa potrebnih za rad this.bookId = bookId;
@Getter this.title = title;
@Setter this.price = price;
@NoArgsConstructor this.amount = amount;
@AllArgsConstructor this.isDeleted = isDeleted;
public class BookDTO { }
private Long bookId; }
private String title;
private double price;
private int amount;
@JsonProperty("isDeleted")
private boolean isDeleted; package com.nikolas.master_thesis.mapper;
private Set<AuthorDTO> authors; import org.mapstruct.Mapper;
private Set<CategoryDTO> categories; import com.nikolas.master_thesis.dto.BookDTO;
import com.nikolas.master_thesis.model.Book;
//...
@Mapper(componentModel = "spring", uses =
{AuthorMapper.class, CategoryMapper.class})
public interface BookMapper {
Book mapBookDTOToBook(BookDTO bookDTO);
BookDTO mapBookToBookDTO(Book book);
}

27
Implementacija SpringBoot aplikacije 9/11
package com.nikolas.master_thesis.controller;
// import-ovanje klasa i interfejsa neophodnih za rad
@RestController

@RestController = public class BookController {
@Autowired
@Controller + @ResponseBody BookService bookService;
@GetMapping("api/books")

@GetMapping public ResponseEntity<BookListDTO> getAllBooks() {
BookListDTO bookListDTO = bookService.getAllBooks();

@PutMapping }
return ResponseEntity.ok(bookListDTO);


@PostMapping @PutMapping("api/books/{id}")
public ResponseEntity<?> updateBook(@RequestBody AddUpdateBookDTO
addUpdateBookDTO, @PathVariable long id) {

@DeleteMapping if (addUpdateBookDTO==null) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} else {
boolean isUpdated = bookService.updateBook(addUpdateBookDTO, id);
if (isUpdated) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE);
}
}
}
}

28
Implementacija SpringBoot aplikacije 10/11
@Override else if (addOrder.getAmount() > book.getAmount()) {
public OrderResponseDTO addOrder(OrderListDTO orderRequest, String username) { throw new StoreException(
if (orderRequest != null) { "Amount for book with title: '" + book.getTitle() +
User user = userRepository.findByUsername(username); "' is more than on the stock!\nCurrent amount on stock is: " +
if (user == null) { book.getAmount(),
throw new StoreException("User '" + username + "' does NOT exist in HttpStatus.BAD_REQUEST);
database!", HttpStatus.NOT_FOUND); } else {
} book.setAmount(book.getAmount() - addOrder.getAmount());
Set<OrderItem> orderItems = new HashSet<>(); order.setTotal(orderRequest.getTotalPrice());
Order order = new Order(); order.setOrderDate(new Timestamp(System.currentTimeMillis()));
List<Long> orderBookIds = orderRequest.getOrders().stream() order.setUser(user);
.map(AddOrderDTO::getBookId).collect(Collectors.toList());
OrderItem orderItem = new OrderItem();
List<Book> booksFromOrder = bookRepository orderItem.setAmount(addOrder.getAmount());
.getAllBooksFromOrder(orderBookIds); orderItem.setBook(book);
Map<Long,Book > booksById = new HashMap<Long, Book>(); orderItem.setOrder(order);
booksFromOrder.forEach(b - > booksById.put(b.getBookId(), b));
order.setOrderItems(orderItems);
for (AddOrderDTO addOrder: orderRequest.getOrders()) { orderItems.add(orderItem);
Book book = booksById.get(addOrder.getBookId()); order = orderRepository.save(order);
if (book == null) { }
throw new StoreException("Error, book with id = " }
+ addOrder.getBookId() + " doesn't exist in database!", return new OrderResponseDTO(order.getOrderId());
HttpStatus.BAD_REQUEST); } else {
} throw new StoreException("Error, request body is empty!", HttpStatus.BAD_REQUEST);
}
}

29
Implementacija SpringBoot aplikacije 11/11
connections = ((core_count * 2) + 1)
connections = broj niti u ConnectionPool-u
core_count= broj logičkih jezgara procesora

spring.datasource.url=jdbc:postgresql://localhost:5432/sb_app_db_ver2
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.maximum-pool-size=5

30
Dropwizard aplikacija

31
Implementacija Dropwizard aplikacije 1/14
Upotrebljene biblioteke:

MapStruct - mapiranje core klasa na api klase i obrnuto

Project Lombok – anotacije za uklanjanje boiler-plate koda

JDBi3 – veća fleksibilnost pisanja SQL upita + upotreba deklarativnog pristupa →
SqlObject interfejsi sa anotiranim metodima

Ručno instanciranje - primena Singleton obrasca

32
Implementacija Dropwizard aplikacije 2/14

33
Implementacija Dropwizard aplikacije 3/14
package com.nikolas.master_thesis.core;
// import-ovanje klasa i interfejsa neophodnih za rad

@Getter, @Setter @Getter
@Setter

@NoArgsConstructor, @NoArgsConstructor
@AllArgsConstructor
public class Book {
@AllArgsConstructor private Long bookId;
private String title;
private double price;
private int amount;
private boolean deleted;
private Set<Author> authors;
private Set<Category> categories;

SqlObject interfejs private Set<OrderItem> orderItems;

@RegisterBeanMapper public Book(Long bookId, String title, double price, int amount, boolean deleted) {
this.bookId = bookId;

@SqlUpdate this.title = title;
this.price = price;
this.amount = amount;

@SqlBatch this.deleted = deleted;
}

@UseRowMapper }

34
Implementacija Dropwizard aplikacije 4/14
package com.nikolas.master_thesis.db; default void iterateAuthorBook(List<Long> existingAuthorIds,
//import-ovane klasa za rad AddUpdateBookDTO bookDTOToSave, Long bookId) {
public interface BookDAO extends SqlObject { List<Long> toBeSavedAuthorIds = bookDTOToSave.getAuthors();
@RegisterBeanMapper(Book.class) List<Long> toBeDeletedAuthorIds = new ArrayList<>();
@SqlUpdate("CREATE TABLE IF NOT EXISTS Book( book_id BIGSERIAL for (Long existAuthorId : existingAuthorIds) {
"PRIMARY KEY, title VARCHAR(255), price double precision, " + if (!toBeSavedAuthorIds.contains(existAuthorId)) {
"amount INTEGER, is_deleted boolean)") toBeDeletedAuthorIds.add(existAuthorId);
void createBookTable(); }
}
@UseRowMapper(BookMapper.class) if (toBeDeletedAuthorIds.size() > 0) {
@SqlQuery("SELECT b.book_id, b.title, b.price, b.amount, b.is_deleted " bulkDeleteAuthorBook(toBeDeletedAuthorIds, bookId);
+ "FROM book b ORDER BY b.book_id") }
List<Book> getAllBooks(); List<Long> toBeInsertedAuthorIds = new ArrayList<>();
for (Long toSaveAId : toBeSavedAuthorIds) {
@SqlUpdate("INSERT INTO author_book(author_id, book_id) VALUES (?, ?)") if (!existingAuthorIds.contains(toSaveAId)) {
void insertAuthorBook(Long authorId, Long bookId); toBeInsertedAuthorIds.add(toSaveAId);
}
@SqlBatch("INSERT INTO author_book(author_id, book_id) VALUES (?, ?)") }
void bulkInsertAuthorBook(List<Long> authorIds, Long bookId); if (toBeInsertedAuthorIds.size() > 0) {
bulkInsertAuthorBook(toBeInsertedAuthorIds, bookId);
@SqlUpdate("DELETE FROM author_book WHERE author_id = ? AND book_id = }
?") }
void deleteAuthorBook(Long authorId, Long bookId); }

35
Implementacija Dropwizard aplikacije 5/14
public class BookMapper implements RowMapper<Book> {
@Override
public Book map(ResultSet rs, StatementContext ctx) throws SQLException {

@RowMapper final long bookId = rs.getLong("book_id");
String title = rs.getString("title");

Batch Insertion double price = rs.getDouble("price");
int amount = rs.getInt("amount");
boolean deleted = rs.getBoolean("is_deleted");

return new Book(bookId, title, price, amount, deleted);


}
}

INSERT INTO author_book(author_id, book_id) VALUES (1, 1); /*= 2ms*/


INSERT INTO author_book(author_id, book_id) VALUES (2, 1); /*= 2ms*/
INSERT INTO author_book(author_id, book_id) VALUES (3, 1); /*= 2ms*/

INSERT INTO author_book(author_id, book_id) VALUES (1, 1),(2,1),(3,1); /*= 2ms*/

36
Implementacija Dropwizard aplikacije 6/14

@RegiserBeanMapper

@UseRowReducer @RegisterBeanMapper(value = OrderItem.class, prefix = "oi")
@RegisterBeanMapper(value = Order.class, prefix = "o")

@SqlQuery @RegisterBeanMapper(value = Book.class, prefix = "b")
@UseRowReducer(OrderItemOrderReducer.class)

@Bind @SqlQuery("SELECT oi.order_item_id oi_order_item_id, "
+ "oi.amount oi_amount, o.order_id "
+ "o_order_id, b.book_id b_book_id "
+ "FROM order_item AS oi "
+ "LEFT JOIN orders AS o ON oi.order_id = o.order_id "
+ "LEFT JOIN book AS b ON oi.book_id = b.book_id "
+ "WHERE oi.order_id = :orderId "
+ "ORDER BY oi.order_id")
List<OrderItem> getAllOrderItemsByOrderId(@Bind("orderId") Long orderId);

37
Implementacija Dropwizard aplikacije 7/14
package com.nikolas.master_thesis.reducers;
//import-ovanje klasa i interfejsa neophondih za rad

LinkedHashMapRowReducer
interfejs public class OrderItemOrderReducer implements LinkedHashMapRowReducer<Long, OrderItem> {

accumulate metod @Override
public void accumulate(Map<Long, OrderItem> container, RowView rowView) {
final OrderItem orderItem = container.computeIfAbsent(
rowView.getColumn("oi_order_item_id", Long.class),
id -> rowView.getRow(OrderItem.class)
);

if (rowView.getColumn("o_order_id", Long.class) != null) {


orderItem.setOrder(rowView.getRow(Order.class));
}
if (rowView.getColumn("b_book_id", Long.class) != null) {
orderItem.setBook(rowView.getRow(Book.class));
}
}
}

38
Implementacija Dropwizard aplikacije 8/14
public boolean deleteAuthor(Long authorId) {
Handle handle = jdbi.open();

Handle objekat try {
AuthorDAO authorDAO = handle.attach(AuthorDAO.class);

Ručna obrada transakcija handle.begin();
handle.getConnection().setAutoCommit(false);

try-catch-finally blok if (authorDAO.deleteAuthor(authorId)) {
handle.commit();
return true;
} else {
throw new Exception("Error, no author with id = " + authorId");
}
} catch (Exception e) {
e.printStackTrace();
handle.rollback();
return false;
} finally {
handle.close();
}
}

39
Implementacija Dropwizard aplikacije 9/14
package com.nikolas.master_thesis.service; //...
// import-ovanje ostalih neophodnih klasa za rad List<Long> existingCategoryIds = categoryDAO
public class BookService { .getCategoriesByBookId(bookId).stream()
private final Jdbi jdbi; .mapToLong(Category::getCategoryId)
private final BookMSMapper bookMSMapper; .boxed().collect(Collectors.toList())
// analogni pristup za “existingAuthorIds” promenljivu
public BookService(Jdbi jdbi, BookMSMapper bookMSMapper) { bookDAO.iterateAuthorBook(existingAuthorIds, bookDTOToUpdate, bookId);
this.jdbi = jdbi; bookDAO.iterateCategoryBook(existingCategoryIds, bookDTOToUpdate,
this.bookMSMapper = bookMSMapper; bookId)
} handle.commit();
return true;
public boolean updateBook(AddUpdateBookDTO bookDTOToUpdate, Long bookId) { } else {
Handle handle = jdbi.open(); throw new Exception("Error, book has NOT been updated!");
try { }
handle.begin(); } else {
handle.getConnection().setAutoCommit(false); throw new Exception("Error, book with id = " + bookId
BookDAO bookDAO = handle.attach(BookDAO.class); + " does NOT exist in database!");
Book searchedBook = bookDAO.getBookById(bookId); }
if (searchedBook != null) { } catch (Exception e) {
if(bookDAO.updateBookDTO(bookId, bookDTOToUpdate.getTitle(), e.printStackTrace();
bookDTOToUpdate.getPrice(), bookDTOToUpdate.getAmount(), handle.rollback();
bookDTOToUpdate.isDeleted())){ return false;
AuthorDAO authorDAO = handle.attach(AuthorDAO.class); } finally {
CategoryDAO categoryDAO = handle.attach(CategoryDAO.class); handle.close();
//... }
}}

40
Implementacija Dropwizard aplikacije 10/14
package com.nikolas.master_thesis.mapstruct_mappers;

@Mapper // import-ovanje klasa i interfejsa neophodnih za rad
@Mapper(componentModel = "default", uses = {AuthorMSMapper.class,

@InheritInverseConfiguration – zbog CategoryMSMapper.class})
upotrebe referentnih kolekcija public interface BookMSMapper {
(AuthorDTO i CategoryDTO) BookMSMapper INSTANCE =Mappers.getMapper(BookMSMapper.class);

Book toBook(BookDTO bookDTO);

@InheritInverseConfiguration
BookDTO fromBook(Book book);
}

41
Implementacija Dropwizard aplikacije 11/14

@Path

@Produces

@GET, @PUT
package com.nikolas.master_thesis.resources; //...
//import-ovanje klasa neophodnih za rad @PUT
@Path("/books") @Path("/{id}")
@Produces(MediaType.APPLICATION_JSON) public Response updateBook(@PathParam("id") Long bookId,
public class BookResource { AddUpdateBookDTO bookDTO) throws DWBException {
private final BookService bookService; BookDTO searchedBook = bookService.getBookById(bookId);
if (searchedBook != null) {
public BookResource(BookService bookService) { boolean isUpdated = bookService.updateBook(bookDTO,
this.bookService = bookService; bookId);
} if (isUpdated) {
return Response.noContent().build();
@GET } else {
public Response getAllBooks() throws DWBException { throw new DWBException(HttpStatus.SC_BAD_REQUEST,
BookListDTO books = bookService.getAllBooks(); "Error, book can"
if (books != null) { + " NOT be saved! Check all fields to be "
return Response.ok(books).build(); + "correctly field!");
} else { }
throw new DWBException(HttpStatus.SC_NOT_FOUND, } else {
"Error, no books have been found!"); throw new DWBException(HttpStatus.SC_NOT_FOUND,
} "Error, book for id "
} + bookId + " has not been found!");
//... }
}

42
Implementacija Dropwizard aplikacije 12/14


DWBException – obična Exception klasa

package com.nikolas.master_thesis.util;
//import-ovanje klasa neophodnih za rad
public class DWBExceptionMapper implements ExceptionMapper <DWBException> {
@Override
public Response toResponse(DWBException exception) {
return Response.status(exception.getCode())
.entity(exception.getMessage())
.type(MediaType.TEXT_PLAIN)
.build();
}
}

43
Implementacija Dropwizard aplikacije 13/14
public synchronized OrderResponseDTO addOrder( else if (addOrder.getAmount() > book.getAmount()) {
OrderListDTO orderRequest, String username) { throw new Exception("Error, it's ONLY possible to order up to "
Handle handle = jdbi.open(); + book.getAmount() + " copies!");
try { } else {
handle.begin(); book.setAmount(book.getAmount() - addOrder.getAmount());
handle.getConnection().setAutoCommit(false); order.setTotal(orderRequest.getTotalPrice());
if (orderRequest != null) { order.setOrderDate(new Timestamp(System.currentTimeMillis()));
UserDAO userDAO = handle.attach(UserDAO.class); order.setUser(user);
User user = userDAO.findUserByUsername(username); order.setOrderItems(orderItems);
if (user == null) {throw new Exception("Error, no user" + if (!bookDAO.updateBookDTO(book.getBookId(), book.getTitle(),
username+"!"); book.getPrice(), book.getAmount(), book.isDeleted())) {
} throw new Exception("Error, book has NOT been updated!");
Set<OrderItem> orderItems = new HashSet<>(); }
Order order = new Order(); OrderItem orderItem = new OrderItem();
BookDAO bookDAO = handle.attach(BookDAO.class); orderItem.setAmount(addOrder.getAmount());
List<Long> orderBookIds = orderRequest.getOrders().stream() orderItem.setBook(book);
.map(AddOrderDTO::getBookId).collect(Collectors.toList()); orderItem.setOrder(order);
List<Book> booksFromOrder = bookDAO orderItems.add(orderItem);
.getAllBooksFromOrder(orderBookIds); }OrderDAO orderDAO = handle.attach(OrderDAO.class);
Map<Long, Book> booksById = new HashMap<>(); order.setOrderItems(orderItems);
booksFromOrder.forEach(b -> booksById.put(b.getBookId(), b)); order = orderDAO.createOrder(order.getTotal(), order.getOrderDate(), user.getUserId());
for (AddOrderDTO addOrder : orderRequest.getOrders()) { OrderItemDAO orderItemDAO = handle.attach(OrderItemDAO.class);
Book book = booksById.get(addOrder.getBookId()); for (OrderItem oi : orderItems) {
if (book == null) { orderItemDAO.createOrderItem(oi.getAmount(),
throw new Exception("Error, book with id " + oi.getBook().getBookId(), order.getOrderId());
addOrder.getBookId() }
+ " does NOT exist in DB!"); handle.commit();
} return new OrderResponseDTO(order.getOrderId());
} else { /* throws exception */ } // catch-finally block } // kraj metoda

44
Implementacija Dropwizard aplikacije 14/14

server: @Override
type: simple public void run(final DropwizardMasterThesisConfiguration configuration,
rootPath: /* final Environment environment) {
applicationContextPath: /api final JdbiFactory jdbiFactory = new JdbiFactory();
adminContextPath: /admin final Jdbi jdbi = jdbiFactory
connector: .build(environment, configuration.getDataSourceFactory(), "postgresql");
type: http jdbi.installPlugin(new PostgresPlugin());
Port: 8080 final BookMSMapper bookMSMapper = BookMSMapper.INSTANCE;
final BookService bookService = new BookService(jdbi, bookMSMapper);
database: final BookResource bookResource = new BookResource(bookService);
driverClass: org.postgresql.Driver environment.jersey().register(new DWBExceptionMapper());
user: postgres environment.jersey().register(new JsonProcessingExceptionMapper(true));
password: postgres environment.jersey().register(bookResource);
url: jdbc:postgresql://localhost:5432/dw_app_db }

45
Vert.X aplikacija

46
Implementacija Vert.X aplikacije 1/18
Osnovni pojmovi u Vert.X framework-u:

Event Loop – glavna nit koja prima event-ove, prosledi registrovanom metodu i asinhrono vrati odgovor,
NE SME se blokirati! Povremeno se zaustavlja i kasnije opet pokreće → štednja resursa!

Verticle – osnovna jedinica za deployment i postoje:

Standard Verticle (Event Loop Thread Pool, size = core_num * 2)

Worker Verticle (Worker Thread Pool, size = 20)

Event Bus – magistrala za razmenu event-ova između komponenata aplikacije na određenim adresama
(String tipa), moguć gubitak poruka → Request-Reply obrasac komunikacije

Handler – osnovni interfejs framework-a

Future – enkapsulacija rezultata operacije (S.O.S. callback hell)

Promise – „writeable side of action“ →enkapsulacija + podržan samo u novijim verzijama Vert.X
framework-a

47
Implementacija Vert.X aplikacije 2/18
Upotrebljene biblioteke:

Vertx-jooq – jOOQ biblioteka prilagođena za rad sa Vert.X framework-om:

generiše POJO i DAO klase, PL/pgSQL procedure itd.

Vert.x-fikovan API (non-blocking implementacija + kompatibilan Vert.X API)


PostgreSQL Reactive Client drajver – non-blocking i reactive implementacija
koda, podrška za RxJava2 itd.

Vert.X Service Proxies – generisanje klasa radi loose coupling-a,
komunikacija komponenata putem Event Bus-a i smanjivanje boiler-plate koda

48
Implementacija Vert.X aplikacije 3/18

49
Implementacija Vert.X aplikacije 4/18
package com.ns.vertx.pg.service;
// import-ovanje ostalih klasa i interfejsa neophodnih za rad

@ProxyGen @ProxyGen
@VertxGen
public interface BookService {

@VertxGen
@GenIgnore

@GenIgnore static BookService createBookService(PgPool pgClient, Configuration configuration,
Handler<AsyncResult<BookService>> readyHandler) {
return new BookServiceImpl(pgClient, configuration, readyHandler);

@Fluent }
@GenIgnore

JsonObject klasa static BookService createBookProxy(Vertx vertx, String address) {
return new BookServiceVertxEBProxy(vertx, address);
}

PgPool interfejs
@Fluent
BookService getAllBooksJooqSP(Handler<AsyncResult<JsonObject>> resultHandler);

@Fluent
BookService updateBookJooqSP(JsonObject bookJO, Handler<AsyncResult<Void>> resultHandler);

50
Implementacija Vert.X aplikacije 5/18
package com.ns.vertx.pg.service;
// import-ovanje ostalih klasa i interfejsa neophodnih za rad
public class BookServiceImpl implements BookService {
private static final Logger LOGGER = LoggerFactory.getLogger(BookServiceImpl.class)
private ReactiveClassicGenericQueryExecutor queryExecutor;

public BookServiceImpl(PgPool pgClient, Configuration configuration,


Handler<AsyncResult<BookService>> readyHandler) {
pgClient.getConnection(ar -> {
if (ar.failed()) {
LOGGER.error("Could NOT OPEN DB connection!", ar.cause());
readyHandler.handle(Future.failedFuture(ar.cause()));
} else {
SqlConnection connection = ar.result();
this.queryExecutor = new ReactiveClassicGenericQueryExecutor(configuration,
pgClient);
LOGGER.info("Connection succeded and queryExecutor instantiation is SUCCESSFUL!");
connection.close();
readyHandler.handle(Future.succeededFuture(this));
}
});
}
// ostali implementirani metodi iz BookService interfejsa
}

51
Implementacija Vert.X aplikacije 6/18
@Override
public BookService getAllBooksJooqSP(Handler<AsyncResult<JsonObject>> resultHandler) {
Future<QueryResult> bookFuture = queryExecutor.transaction(transactionQE -> {
return transactionQE.query(dsl -> dsl
.select(BOOK.BOOK_ID, BOOK.TITLE, BOOK.PRICE, BOOK.AMOUNT, BOOK.IS_DELETED,
DSL.field("to_json(array_agg(DISTINCT {0}.*))", JSON.class,
AUTHOR).as("authors"),
DSL.field("to_json(array_agg(DISTINCT {0}.*))", JSON.class,
CATEGORY).as("categories")
).from(BOOK
.leftJoin(AUTHOR_BOOK).on(BOOK.BOOK_ID.eq(AUTHOR_BOOK.BOOK_ID))
.leftJoin(AUTHOR).on(AUTHOR_BOOK.AUTHOR_ID.eq(AUTHOR.AUTHOR_ID))
.leftJoin(CATEGORY_BOOK).on(BOOK.BOOK_ID.eq(CATEGORY_BOOK.BOOK_ID))
.leftJoin(CATEGORY).on(CATEGORY_BOOK.CATEGORY_ID.eq(CATEGORY.CATEGORY_ID))
).groupBy(BOOK.BOOK_ID).orderBy(BOOK.BOOK_ID.asc())
);
});
bookFuture.onComplete(handler - > {
if (handler.succeeded()) {
QueryResult booksQR = handler.result();
JsonObject booksJsonObject = extractBooksFromQR(booksQR);
resultHandler.handle(Future.succeededFuture(booksJsonObject));
} else {
queryExecutor.rollback();
resultHandler.handle(Future.failedFuture(new NoSuchElementException("There are "
+ "no saved books in database!")));
}
});
return this;
}

52
Implementacija Vert.X aplikacije 7/18
public BookService updateBookJooqSP(JsonObject bookJO, Handler<AsyncResult<Void>> resultHandler){
Set<Long> authorUpdatedIds = bookJO.getJsonArray("authors").stream().mapToLong(a ->
Long.valueOf(String.valueOf(a))).boxed().collect(Collectors.toSet());
Set<Long> categoryUpdatedIds = bookJO.getJsonArray("categories").stream().mapToLong(a ->
Long.valueOf(String.valueOf(a))).boxed().collect(Collectors.toSet());
Long bookId = bookJO.getLong("book_id");
Book bookPojo = new Book(bookJO);
queryExecutor.beginTransaction().compose(transcationQE ->
transcationQE.execute(dsl - > dsl
.update(BOOK).set(BOOK.TITLE, bookPojo.getTitle())
.set(BOOK.PRICE, bookPojo.getPrice()).set(BOOK.AMOUNT,
bookPojo.getAmount()).set(BOOK.IS_DELETED,bookJO.getBoolean("isDeleted"))
.where(BOOK.BOOK_ID.eq(Long.valueOf(bookId)))
).compose(res -> CompositeFuture.all(
iterateCategoryBook(transcationQE, categoryUpdatedIds, bookId),
iterateAuthorBook(transcationQE, authorUpdatedIds, bookId)
).compose(success - > {
return transcationQE.commit();
}, failure - > {
return transcationQE.rollback()
.onSuccess(handler - > LOGGER.debug("Transaction successfully rolled-back!"))
.onFailure(handler - > LOGGER.error("Error, transaction FAILED to rollback!"));
})))
.onSuccess(handler - > resultHandler.handle(Future.succeededFuture()))
.onFailure(handler - > resultHandler.handle(Future.failedFuture(handler)));
return this;
}

53
Implementacija Vert.X aplikacije 8/18
private Future <Void> iterateAuthorBook(ReactiveClassicGenericQueryExecutor queryExecutor, Set<Long> authorUpdatedIds,
long bookId) {
Promise<Void> promise = Promise.promise();
Future<Integer> iterateABFuture = queryExecutor.findManyRow(dsl -> dsl
.select(AUTHOR_BOOK.AUTHOR_ID).from(AUTHOR_BOOK)
.where(AUTHOR_BOOK.BOOK_ID.eq(Long.valueOf(bookId)))).compose(existingAC -> {
Set<Long> existingBAuthorIds = extractAuthorsFromLR(existingAC);
Set<Long> deleteAutIdsSet = existingBAuthorIds.stream()
.filter(autId - > !authorUpdatedIds.contains(autId))
.collect(Collectors.toSet());
Set<Long> toInsertAutIdsSet = authorUpdatedIds.stream()
.filter(catId -> !existingBAuthorIds.contains(catId))
.collect(Collectors.toSet());

if (!toInsertAutIdsSet.isEmpty() && !deleteAutIdsSet.isEmpty()) {


return queryExecutor.execute(dsl - > dsl.deleteFrom(AUTHOR_BOOK)
.where(AUTHOR_BOOK.BOOK_ID.eq(Long.valueOf(bookId)))
.and(AUTHOR_BOOK.AUTHOR_ID.in(deleteAutIdsSet))
).compose(res - > queryExecutor.execute(dsl - > {
CommonTableExpression <Record2<Long,Long>> author_book_tbl =
author_book_tbl(dsl,bookId,toInsertAutIdsSet);
return dsl.with(author_book_tbl).insertInto(AUTHOR_BOOK,
AUTHOR_BOOK.BOOK_ID, AUTHOR_BOOK.AUTHOR_ID)
.select(dsl.selectFrom(author_book_tbl));
})).mapEmpty();
} else if (!toInsertAutIdsSet.isEmpty() && deleteAutIdsSet.isEmpty()) {
// samo se vrši brisanje primarnih ključeva za author_id i book_id
} else if (toInsertAutIdsSet.isEmpty() && !deleteAutIdsSet.isEmpty()) {
// samo se vrši unos novih primarnih ključeva za author_id i jedan book_id
} else { // nema izmena
return Future.succeededFuture();
}
});
iterateABFuture.onSuccess(handler - > promise.complete())
.onFailure(handler - > promise.fail(handler));
return promise.future();
}

54
Implementacija Vert.X aplikacije 9/18

crossJoin metod se koristi da se kreira Dekartov proizvod
book_id i kolekcije author_id elemenata

private CommonTableExpression<Record2<Long, Long>> author_book_tbl(DSLContext dsl,


Long bookId, Set<Long> toInsertAutIdsSet) {
return DSL.name("author_book_tbl").fields("book_id", "author_id")
.as(dsl.select(BOOK.BOOK_ID, AUTHOR.AUTHOR_ID).from(BOOK).crossJoin(AUTHOR)
.where(BOOK.BOOK_ID.eq(bookId).and(AUTHOR.AUTHOR_ID.in(toInsertAutIdsSet))));
}

private Set<Long> extractAuthorsFromLR(List <Row> authorLR) {


Set<Long> authorIds = new HashSet<>();
for (Row row: authorLR) {
authorIds.add(row.getLong("author_id"));
}
return authorIds;
}

55
Implementacija Vert.X aplikacije 10/18

@Override
public AuthorService deleteAuthorJooqSP(Long id,
Handler <AsyncResult<Void>> resultHandler) {
Future<Integer> deleteAuthorFuture = queryExecutor.transaction(transactionQE -> {
return transactionQE.execute(dsl - > dsl
.delete(AUTHOR)
.where(AUTHOR.AUTHOR_ID.eq(Long.valueOf(id))));
});
deleteAuthorFuture.onComplete(ar - > {
if (ar.succeeded()) {
resultHandler.handle(Future.succeededFuture());
} else {
queryExecutor.rollback().onFailure(handler - >
resultHandler.handle(Future.failedFuture(new NoSuchElementException("No author with id = " + id)));
}
});
return this;
}

56
Implementacija Vert.X aplikacije 11/18
package com.ns.vertx.pg.service;
// import-ovanje ostalih klasa i interfejsa neophodnih za rad
public class DatabaseVerticle extends AbstractVerticle {

@PgConnectOptions public static final String CONFIG_BOOK_QUEUE = "book.queue";
klasa @Override
public void start(Promise<Void> startPromise) throws Exception {

@PgPool interfejs PgConnectOptions connectOptions = new PgConnectOptions()
.setPort(5432)
.setHost("localhost")

ServiceBinder klasa .setDatabase("vertx-jooq-cr")
.setUser("postgres").setPassword("postgres");

CONFIG_BOOK_QUEUE PoolOptions poolOptions = new PoolOptions().setMaxSize(5);
PgPool pgClient = PgPool.pool(vertx, connectOptions, poolOptions);
Configuration configuration = new DefaultConfiguration();
configuration.set(SQLDialect.POSTGRES);
BookService.createBookService(pgClient, configuration, readyHandler -> {
if (readyHandler.succeeded()) {
ServiceBinder binder = new ServiceBinder(vertx);
binder.setAddress(CONFIG_BOOK_QUEUE).register(BookService.class, readyHandler.result());
startPromise.complete();
} else {
startPromise.fail(readyHandler.cause());
}
});
}
}

57
Implementacija Vert.X aplikacije 12/18
package com.ns.vertx.pg.http; server.exceptionHandler(handler - > LOGGER
// import-ovanje ostalih klasa i interfejsa neophodnih za rad .error("exceptionHandler triggered, " +
public class HttpServerVerticle extends AbstractVerticle { handler.getCause()));
private static int LISTEN_PORT = 8080; server.requestHandler(routerAPI).listen(portNumber,
private static final String CONFIG_HTTP_SERVER_PORT = ar -> {
"http.server.port"; if (ar.succeeded()) {
public static final String CONFIG_BOOKDB_QUEUE = "book.queue"; startPromise.complete();
private BookService bookService; } else {//dfdf
startPromise.fail(ar.cause());
@Override }
public void start(Promise <Void> startPromise) throws Exception { });
String bookAddress = config().getString(CONFIG_BOOKDB_QUEUE, }
"book.queue");
bookService = BookService.createBookProxy(vertx, bookAddress); private void deleteAuthorHandler(RoutingContext rc) {
Router routerREST = Router.router(vertx); Long id = Long.valueOf(rc.request().getParam("id"));
//.. authorService.deleteAuthorJooqSP(id, noContent(rc));
}
routerREST.delete("/authors/:id")
.handler(this::deleteAuthorHandler); private void getAllBooksHandlerJooq(RoutingContext rc) {
routerREST.get("/books").handler(this::getAllBooksHandlerJooq); bookService.getAllBooksJooqSP(ok(rc));
// ... }
routerREST.get("/orders")
.blockingHandler(this::getAllOrdersHandler,false); private void getAllOrdersHandler(RoutingContext rc) {
routerREST.post("/orders").handler(this::createOrdersHandler) orderService.getAllOrdersJooqSP(ok(rc));
Router routerAPI = Router.router(vertx); }
private void createOrderHandler(RoutingContext rc) {
routerAPI.mountSubRouter("/api", routerREST); JsonObject orderJO = rc.getBodyAsJson();
HttpServer server = vertx.createHttpServer(); MultiMap parameters = rc.request().params();
int portNumber = config().getInteger(CONFIG_HTTP_SERVER_PORT, String username = parameters.get("username");
LISTEN_PORT); orderService.createOrderJooqSP(orderJO, username,
created(rc));
}
}

58
Implementacija Vert.X aplikacije 13/18
package com.ns.vertx.pg.http; static Handler<AsyncResult<Void>> noContent(RoutingContext rc) {
// import-ovanje klasa i interfejsa neophodnih za rad return ar - > {
public class ActionHelper { if (ar.failed()) {
if (ar.cause() instanceof NoSuchElementException) {
private static <T> Handler <AsyncResult<T>> writeJsonResponse( rc.response().setStatusCode(404)
RoutingContext context, int status) { .end(ar.cause().getMessage());
return ar - > { } else {
if (ar.failed()) { rc.fail(ar.cause());
if (ar.cause() instanceof NoSuchElementException) { }
context.response().setStatusCode(404) } else {
.end(ar.cause().getMessage()); rc.response().setStatusCode(204).end();
} else if (ar.cause() instanceof IOException) { }
context.response().setStatusCode(400) };
.end(ar.cause().getMessage()); }
} else {
context.fail(ar.cause()); }
}
} else {
context.response().setStatusCode(status).putHeader("content-type",
"application/json; charset=utf-8")
.end(Json.encodePrettily(ar.result()));
}
};
}

static <T> Handler <AsyncResult<T>> ok(RoutingContext rc) {


return writeJsonResponse(rc, 200);
}

static <T> Handler <AsyncResult<T>> created(RoutingContext rc) {


return writeJsonResponse(rc, 201);
}

59
Implementacija Vert.X aplikacije 14/18
@Override
public OrderService getAllOrdersJooqSP(Handler<AsyncResult<JsonObject>> resultHandler) {
Connection connection = null;
try {
connection = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/vertx-jooq-cr",
"postgres", "postgres");
Settings settings = new Settings().withExecuteLogging(false);
DSLContext dslContext = DSL.using(connection, SQLDialect.POSTGRES, settings);
Result<Record1<String>> resultR1S = dslContext.select(Routines.getAllOrders()).fetch();
String strResultFinal = resultR1S.formatJSON(
new JSONFormat().header(false).recordFormat(RecordFormat.ARRAY));
final String fixedJSONString = strResultFinal.substring(3, strResultFinal.length() - 3)
.replaceAll("\\\\n", "")
.replaceAll("\\\\", "");

JsonObject ordersJA = new JsonObject(fixedJSONString);


connection.close();
resultHandler.handle(Future.succeededFuture(ordersJA));
} catch (SQLException e) {
e.printStackTrace();
}
return this;
}

60
Implementacija Vert.X aplikacije 15/18
CREATE OR REPLACE FUNCTION get_all_orders() RETURNS JSON AS
$BODY$
DECLARE
single_order RECORD;
single_order_json json;
orders_array json[];
BEGIN
FOR single_order IN SELECT * FROM public.orders ORDER BY order_id
LOOP
SELECT get_order_by_order_id(single_order.order_id) INTO
single_order_json;
orders_array = array_append(orders_array, single_order_json);
END LOOP;
return (select json_build_object(
'orders', orders_array
));
END;
$BODY$
LANGUAGE 'plpgsql';

61
@Override
Implementacija Vert.X aplikacije 16/18
public OrderService createOrderJooqSP(JsonObject orderJO, String username,
Handler <AsyncResult<JsonObject>> resultHandler) {
if (orderJO == null) {
resultHandler.handle(Future.failedFuture(new IOException("Body is empty!")));
}

Future<JsonObject> retVal = queryExecutor.beginTransaction().compose(transactionQE ->


transactionQE.executeAny(dsl - > dsl
.selectFrom(USERS).where(USERS.USERNAME.eq(username))
).compose(userRes - > {
Users userPojo = getUserPojoFromRS(userRes);
List <JsonObject> orderItemJObjectsToSave = extractOrderItemsFromOrderJA(orderJO
.getJsonArray("orders"));
List <Long> orderItemBookIds = orderItemJObjectsToSave.stream()
.mapToLong(oi - > oi.getLong("book_id")).boxed().collect(Collectors.toList());
Map <Long,Integer> bookIdAmountMap = mapOrderItemsFromOrderJA(orderJO
.getJsonArray("orders"));
// kreiranje "orderDate" polja
return transactionQE.executeAny(/* insertInto naredba u "orders" tabelu */)
).compose(savedOrder - > {
Orders savedOrderPojo = extractOrderRS(savedOrder);
return transactionQE.executeAny(dsl - > dsl
.select(BOOK.BOOK_ID, BOOK.PRICE, BOOK.TITLE, BOOK.AMOUNT,
BOOK.IS_DELETED).from(BOOK)
.where(BOOK.BOOK_ID.in(orderItemBookIds))
).compose(searchedBooksRS - > {
List<Book> searchedBookList = extractBookPojosFromRS(searchedBooksRS);
Map<Long,Integer> bookIdAmountMapUpdated = new LinkedHashMap < > ();
for (Book sb: searchedBookList) {
int updatedBookAmount = sb.getAmount() - bookIdAmountMap.get(sb.getBookId());
if (updatedBookAmount < 0) {
resultHandler.handle(Future.failedFuture(new IOException(
"Erro, too many books!")));
transactionQE.rollback();
}
bookIdAmountMapUpdated.put(sb.getBookId(), updatedBookAmount);
}

62
Implementacija Vert.X aplikacije 17/18
return transactionQE.execute(dsl - > {
Row2<Long,Integer> array[] = new Row2[bookIdAmountMapUpdated.size()];
int i = 0;
for (Map.Entry<Long,Integer> pair: bookIdAmountMapUpdated.entrySet()) {
array[i] = DSL.row(
DSL.val(pair.getKey()).cast(SQLDataType.BIGINT),
DSL.val(pair.getValue()).cast(SQLDataType.INTEGER));
i++;
}
Table<Record2<Long,Integer >> batTmp = DSL.values(array);
batTmp = batTmp.as("bat", "book_id", "amount");
Field<Long> bookIdField = DSL.field(DSL.name("bat", "book_id"), Long.class);
Field<Integer> amountField = DSL.field(DSL.name("bat", "amount"), Integer.class);
return dsl.update(BOOK).set(BOOK.AMOUNT, amountField)
.from(batTmp)
.where(BOOK.BOOK_ID.eq(bookIdField));
}).compose(updatedBooks -> {
Long orderId = savedOrderPojo.getOrderId();
Row3 <Long,Long,Integer > oiArr[] = new Row3[bookIdAmountMap.size()];
int i = 0;
for (Map.Entry<Long, Integer> pair: bookIdAmountMap.entrySet()) {
oiArr[i] = DSL.row(DSL.val(orderId).cast(SQLDataType.BIGINT),
DSL.val(pair.getKey()).cast(SQLDataType.BIGINT),
DSL.val(pair.getValue()).cast(SQLDataType.INTEGER));
i++;
}
Table <Record3<Long,Long,Integer>> oiTmp = DSL.values(oiArr)
.as("oiTmp", "order_id", "book_id", "amount");
return transactionQE.execute(dsl - > dsl
.insertInto(ORDER_ITEM, ORDER_ITEM.ORDER_ID, ORDER_ITEM.BOOK_ID,
ORDER_ITEM.AMOUNT).select(dsl.selectFrom(oiTmp))
).compose(success - > {
transactionQE.commit();
return Future.succeededFuture(
new JsonObject().put("orderId",orderId));
},failure - > { /*roll-back transaction*/ } }); }); }); }); }));
retVal.onSuccess(result - > resultHandler.handle(Future.succeededFuture(result)))
.onFailure(handler - > resultHandler.handle(Future.failedFuture(handler)));
return this;
}

63
Implementacija Vert.X aplikacije 18/18
package com.ns.vertx.pg;

public class MainVerticle extends AbstractVerticle {

private static final int HTTP_INSTANCE_NUM = 4; package com.ns.vertx.pg;


//import-ovanje klasa i interfejsa neophodnih za rad
@Override public class ApplicationLauncher extends Launcher {
public void start(Promise<Void> startPromise) throws Exception {
Promise<String> dbVerticleDepoyment = Promise.promise(); public static void main(String[] args) {
vertx.deployVerticle(new DatabaseVerticle(), dbVerticleDepoyment); new ApplicationLauncher().dispatch(args);
dbVerticleDepoyment.future().compose(ar -> { }
Promise<String> httpVerticleDeployment = Promise.promise();
vertx.deployVerticle(HttpServerVerticle.class.getName(), @Override
new DeploymentOptions().setInstances(HTTP_INSTANCE_NUM), public void beforeStartingVertx(VertxOptions options) {
httpVerticleDeployment); options.setMetricsOptions(new MicrometerMetricsOptions()
return httpVerticleDeployment.future(); .setJmxMetricsOptions(
}).onComplete(handler -> { new VertxJmxMetricsOptions().setEnabled(true)
if (handler.succeeded()) { .setStep(1).setDomain("vertx.micrometer.metrics")
startPromise.complete(); )
} else { .setJvmMetricsEnabled(true).setEnabled(true)
startPromise.fail(handler.cause()); );
} }
}); }
}
}

64
3. TESTIRANJE

65
Testiranje

Konfiguracija računara na kome su vršeni razvoj i testiranje:



Procesor – Intel i5-4200U (2 fizička jezgra/4 logička jezgra → Hyperthreading)

RAM memorija – 8GB RAM DDR3 memorija

Hard disk – HDD SATA, 500 GB

Operativni sistem – Fedora31, 64-bit

JVM verzija - Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed
mode)

66
Kreiranje testova u JMeter alatu 1/4
Važne stavke Concurrency Thread Group-e:

Target Concurrency

Ramp Up Time

Ramp-Up Steps Count

Hold Target Rate Time
Scenariji za sl. brojeve korisnika:

100

250

500

750

67
Kreiranje testova u JMeter alatu 2/4
Važne stavke HTTP Request Sampler elementa:

Server Name or IP

Port Number

Method

Path

Use KeepAlive

Body Data

Listener elementi unutar HTTP Request Sampler elementa:



View Results Tree

JMXMon Samples Collector

PerfMon Metrics Collector
68
Kreiranje testova u JMeter alatu 3/4
Konvencija imenovanja promenljivih u JSON notaciji:

Camel case

Camel case + Snake case

{ {
"orders": [ "orders": [
{ {
"bookId": ${bookId1}, "book_id": ${bookId1},
"amount": ${amount1} "amount": ${amount1}
}, },
{ {
"bookId": ${bookId2}, "book_id": ${bookId2},
"amount": ${amount2} "amount": ${amount2}
}], }],
"totalPrice": ${totalPrice} "totalPrice": ${totalPrice}
} }

69
Kreiranje testova u JMeter alatu 4/4
Broj recorda u tabelama: -Testiranje izvršeno u NON-GUI režimu

authors #14 - Komande za startovanje aplikacija:

books #17

SpringBoot i Vert.X
java -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=4711 \

categories #17 -Dcom.sun.management.jmxremote.rmi.port=4711 \
-Dcom.sun.management.jmxremote.authenticate=false \

orders #9 -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -jar \
./target/naziv_projekta_version.jar

order_items #18

users #3 ●
Dropwizard
java -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=4711 \
-Dcom.sun.management.jmxremote.rmi.port=4711 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false Djava.rmi.server.hostname=127.0.0.1 \
-jar ./target/DW-Bookshop-1.0-SNAPSHOT.jar server config.yml

70
4. DISKUSIJA DOBIJENIH
REZULTATA

71
Diskusija dobijenih rezultata

Testirani metodi: Prikaz dobijenih rezultata



getSingleBook ●
Tabela performansi

getAllBooks ●
Grafik potrošnje procesora

getAllOrders ●
Grafik potrošnje Heap memorije

createOrder ●
Grafik potrošnje Non-Heap memorije

deleteAuthor

72
getSingleBook metod (100 i 250 korisnika) 1/8

Rezultati preformansi za 100 i 250 korisnika:



#Samples - Rang performansi:
1. Dropwizard prednjači
2. Vert.X
3. SpringBoot

Std. Dev. - Vert.X ima procentualno najmanju vrednost za 250 korisnika, a SpringBoot za
100 korisnika

Maximum – Vert.X ima najveće vrednosti → ne utiče na performanse (JVM byte code
optimization)

73
getSingleBook metod (100 i 250 korisnika) 2/8

Aplikacija #Samples Averag 95% Min Maximum Error % Std. Dev. Throughput Br.
e kb/s korisnika
SpringBoot 22533 136 166 4 1084 0.00 28.59 561.7 100

Dropwizard 24340 123 204 6 1134 0.00 49.34 611

Vert.X 22982 132 197 22 1746 0.00 49.13 575.5


SpringBoot 82689 228 290 8 869 0.00 48.84 824.9 250

Dropwizard 87673 217 330 5 1031 0.00 69.15 869.7

Vert.X 84913 224 262 26 1447 0.00 39.55 840.9

74
getSingleBook metod (100 i 250 korisnika) 3/8

Potrošnja Heap i Non-Heap memorije je za različite brojeve korisnika je slična. Vidljive


razlike su:

Heap memorija – Vert.X aplikacija troši 3-4 puta manju količinu ove memorije

Non-Heap memorija – Dropwizard i Vert.X aplikacija troše približno istu količinu
ove memorije

75
getSingleBook metod (100 i 250 korisnika) 4/8

76
getSingleBook metod (500 i 750 korisnika) 5/8

Rezultati preformansi za 500 i 750 korisnika:



#Samples - Rang performansi:
1. Dropwizard prednjači
2. SpringBoot
3. Vert.X (performanse degradiraju povećanjem broja korisnika)

Std. Dev. - SpringBoot ima procentualno najmanju vrednost → najkonzistentniji odziv

Maximum – SpringBoot ima najveće vrednosti za oba broja korisnika

77
getSingleBook metod (500 i 750 korisnika) 6/8

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika

SpringBoot 228639 329 438 12 1593 0.00 82.77 1141.1


500
Dropwizard 231200 323 551 3 1040 0.00 126.31 1153.5

Vert.X 188989 399 507 42 1398 0.00 117.30 941.6


SpringBoot 365842 460 603 2 1934 0.00 134.59 1215.9 750

Dropwizard 374781 446 694 2 1292 0.00 147.97 1245

Vert.X 293687 579 753 14 1437 0.00 200.59 974

78
getSingleBook metod (500 i 750 korisnika) 7/8

Potrošnja Heap i Non-Heap memorije je za različite brojeve korisnika je slična. Vidljive


razlike su:

Heap memorija – potrošnja se duplira za Vert.X aplikaciju

Non-Heap memorija:

Dropwizard i Vert.X aplikacija troše približno istu količinu ove memorije od ~40MB
do ~70MB

SpringBoot troši od ~100MB do ~130MB što je ~2.5 puta veća potrošnja ove
memorije od druge dve aplikacije

79
getSingleBook metod (500 i 750 korisnika) 8/8

80
getAllBooks metod (100 i 250 korisnika) 1/8

Rezultati preformansi za 100 i 250 korisnika:



#Samples - Rang performansi:
1. Vert.X
2. SpringBoot
3. Dropwizard ~2.5 puta slabiji odziv od Vert.X aplikacije

Min - Vert.X ima bolje vrednosti za 250 nego za 100 korisnika

Maximum – Vert.X ima najveće vrednosti za 250 korisnika, SpringBoot za 100
korisnika

81
getAllBooks metod (100 i 250 korisnika) 2/8

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika

SpringBoot 18291 169 214 247 1311 0.00 36.74 453.8 100

Dropwizard 8564 360 511 97 1485 0.00 92.41 211.8

Vert.X 21282 143 212 20 1384 0.00 46.09 532.9

SpringBoot 67182 283 390 53 1050 0.00 64.43 666.6 250

Dropwizard 24584 778 1071 68 1361 0.00 234.55 242.6

Vert.X 74211 257 307 17 1401 0.00 51.54 736.1

82
getAllBooks metod (100 i 250 korisnika) 3/8

Potrošnja Heap i Non-Heap memorije je za različite brojeve korisnika je slična. Vidljive


razlike su:

Heap memorija – Vert.X aplikacija troši 3-4 puta manju količinu ove memorije, a skoro
se duplira kad aplikaciju koristi 250 korisnika

Non-Heap memorija – Dropwizard i Vert.X aplikacija troše približno istu količinu ove
memorije

83
getAllBooks metod (100 i 250 korisnika) 4/8

84
getAllBooks metod (500 i 750 korisnika) 5/8

Rezultati preformansi za 500 i 750 korisnika:



#Samples - Rang performansi:
1. SpringBoot
2. Vert.X ~10% slabiji odziv od SpringBoot za 500 korisnika , 20% slabiji odziv od
SpringBoot za 750 korisnika
3. Dropwizard sad ima ~3 puta slabiji odziv od SpringBoot aplikacije za 500 korisnika, ~4
puta slabiji odziv od SpringBoot aplikacije za 750 korisnika

Std. Dev. - SpringBoot ima procentualno najmanju vrednost

Maximum – Dropwizard ima najveće vrednosti, pre je Vert.X imao za 100 i 250 korisnika

85
getAllBooks metod (500 i 750 korisnika) 6/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika

SpringBoot 179238 422 528 45 1099 0.00 75.26 891.5


500
Dropwizard 55301 1373 1726 80 2209 0.00 398.54 273.4

Vert.X 161152 468 598 35 1410 0.00 145.12 802.8

SpringBoot 308055 549 685 26 2062 0.00 145.83 1023.7 750

Dropwizard 86661 1961 2497 116 2800 0.00 620.77 286.7

Vert.X 248836 683 899 40 1470 0.00 238.48 825.2

86
getAllBooks metod (500 i 750 korisnika) 7/8

Potrošnja Heap i Non-Heap memorije je za različite brojeve korisnika je slična. Vidljive


razlike su:

Heap memorija – Vert.X aplikacija troši ~720MB za 750 korisnika, dok za 500
korisnika troši ~400MB

Non-Heap memorija – Dropwizard i Vert.X aplikacija troše približno istu količinu ove
memorije → isto kao i pre

87
getAllBooks metod (500 i 750 korisnika) 8/8

88
getAllOrders metod (100 i 250 korisnika) 1/10

Rezultati preformansi za 100 i 250 korisnika:



#Samples - Rang performansi:
1. SpringBoot
2. Dropwizard ~2.5x manje od SpringBoot aplikacije za 100 korisnika; ~3x manje od
SpringBoot aplikacije za 250 korisnika
3. Vert.X ~3.5x manje od SpringBoot aplikacije za 100 korisnika; i ~4.7x manje od SpringBoot
aplikacije za 250 korisnika→ String metodi u getAllOrdersJooqSP metodu u Vert.X aplikaciji

Std. Dev. - Vert.X ima ¼ vrednosti Average polja

89
getAllOrders metod (100 i 250 korisnika) 2/10

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
SpringBoot 6871 454 542 205 1397 0.00 65.67 168.8 100

Dropwizard 2896 1091 1396 465 2237 0.00 227.61 70.0

Vert.X 1913 1676 2095 433 2201 0.00 457.71 45.6


SpringBoot 24458 784 982 218 1428 0.00 168.6 240.8 250
Dropwizard 8218 2350 3193 246 3558 0.00 685.37 79.7

Vert.X 5208 3771 5105 272 5616 0.00 1383.6 49.6

90
getAllOrders metod (100 i 250 korisnika) 3/10
Potrošnja memorije i opterećenje procesora:

Heap memorija – potrošnja Vert.X aplikacije se povećava za ~25% za 250 korisnika

Non-Heap memorija – nema značajnih promena

Opterećenje procesora – primetno manje optrećenje u Vert.X aplikaciji → upotreba
blocking implementacije i niti iz Worker Thread Pool-a

91
getAllOrders metod (100 i 250 korisnika) 4/10

92
getAllOrders metod (100 i 250 korisnika) 5/10

93
getAllOrders metod (500 i 750 korisnika) 6/10

Rezultati preformansi za 500 i 750 korisnika:



#Samples - Rang performansi:
1. SpringBoot
2. Dropwizard ~3.5x slabiji odziv od SpringBoot aplikacije za oba broja korisnika
3. Vert.X ~6.24x slabiji odziv od SpringBoot aplikacije za oba broja korisnika

Std. Dev. - Vert.X iznosi skoro ½ vrednosti Average polja → konzistentnost odziva
podataka se povećanjem broja korisnika smanjuje

94
getAllOrders metod (500 i 750 korisnika) 7/10

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
SpringBoot 65670 1155 1537 165 1747 0.00 263.76 325.7
500
Dropwizard 18422 4157 5556 295 5949 0.00 1314.04 90.0

Vert.X 10522 7374 9991 241 10336 0.00 2924.01 50.3

SpringBoot 105289 1616 1910 214 2124 0.00 369.90 348.2 750

Dropwizard 29777 5768 7149 271 7310 0.00 1840.44 97.1

Vert.X 16339 10691 14056 313 14317 0.00 4274.13 52.0

95
getAllOrders metod (500 i 750 korisnika) 8/10

Potrošnja memorije i opterećenje procesora:



Heap memorija – potrošnja Vert.X aplikacije se udvostručuje u odnosu za 100 i 250
korisnika

Non-Heap memorija – nema značajnih promena

Opterećenje procesora – primetno manje optrećenje u Vert.X aplikaciji i peak-ovi u
radu jezgara → povećanje Standardne Devijacije

96
getAllOrders metod (500 i 750 korisnika) 9/10

97
getAllOrders metod (500 i 750 korisnika) 10/10

98
createOrder metod (100 i 250 korisnika) 1/8

Rezultati preformansi za 100 i 250 korisnika:



#Samples - Rang performansi:
1. Vert.X
2. SpringBoot ~21.4% slabiji odziv od Vert.X aplikacije za oba broja korisnika
3. Dropwizard ~2.5x slabiji odziv od Vert.X aplikacije za oba broja korisnika

Std. Dev. - Dropwizard prelazi dozvoljenu granicu → 61% Average polja

99
createOrder metod (100 i 250 korisnika) 2/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika

SpringBoot 5128 616 827 79 1720 0.00 176.97 124.1 100

Dropwizard 2625 1212 2465 21 3207 0.00 741.51 63.2

Vert.X 6525 481 659 149 2413 0.00 146.05 160.8


SpringBoot 13832 1390 2010 99 2499 0.00 519.80 135.7 250

Dropwizard 7450 2615 5873 20 6737 0.00 1783.48 71.8

Vert.X 18290 1050 1432 128 1812 0.00 386.50 179.5

100
createOrder metod (100 i 250 korisnika) 3/8

Potrošnja memorije:

Heap memorija – mala potrošnja Vert.X aplikacije, ostale dve su slične prethodno
testiranim metodima

Non-Heap memorija – Vert.X i Dropwizard aplikacija su slične

101
createOrder metod (100 i 250 korisnika) 4/8

102
createOrder metod (500 i 750 korisnika) 5/8

Rezultati preformansi za 500 i 750 korisnika:



#Samples - Rang preformansi:
1. Vert.X
2. SpringBoot ~ 24% manje od Vert.X aplikacije za 500 korisnika
3. Dropwizard ~ 2.4x slabije od Vert.X-a za 500 i 750 korisnika

Std. Dev. - Dropwizard prelazi dozvoljenu granicu → 61% Average polja

103
createOrder metod (500 i 750 korisnika) 6/8

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
SpringBoot 28482 2685 3688 98 4256 0.00 1061.86 139.6
500
Dropwizard 15694 4919 11379 13 13052 0.00 3522.93 75.9

Vert.X 37376 2038 2787 154 2945 0.00 782.79 184.2

SpringBoot 42942 4005 5546 85 6196 0.00 1621.21 140.2 750

Dropwizard 23763 7275 17121 13 19362 0.00 5329.95 76.8

Vert.X 56671 3018 4191 137 4645 0.00 1225.08 186.3

104
createOrder metod (500 i 750 korisnika) 7/8

Potrošnja memorije:

Heap memorija – mala potrošnja Vert.X aplikacije → nema duplog uvećanja;
potrošnja za ostale dve aplikacije su slične prethodno testiranim metodima

Non-Heap memorija – potrošnja za Vert.X i Dropwizard aplikaciju je slična;
SpringBoot „standardna“ potrošnja ~140MB

105
createOrder metod (500 i 750 korisnika) 8/8

106
deleteAuthor metod (100 i 250 korisnika) 1/8

Rezultati preformansi za 100 i 250 korisnika:



#Samples - Rang performansi:
1. Vert.X (blaga prednost u odnosu na preostale dve aplikacije)
2. SpringBoot
3. Dropwizard

Std. Dev. - proporcionalno slična za različite brojeve korisnika

Maximum – SpringBoot aplikacija ima najveće vrednosti za 100 i 250 korisnika

107
deleteAuthor metod (100 i 250 korisnika) 2/8

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika

SpringBoot 7938 396 588 32 1013 0.00 150.74 193.7 100

Dropwizard 7756 406 589 31 800 0.00 145.38 189.1

Vert.X 8029 389 567 37 930 0.00 143.47 198.5


SpringBoot 19934 964 1333 32 1592 0.00 382.23 195.7 250
Dropwizard 19800 967 1332 36 1434 0.00 370.69 195.1

Vert.X 20269 947 1342 37 1495 0.00 389.18 199.5

108
deleteAuthor metod (100 i 250 korisnika) 3/8

Potrošnja memorije:

Heap memorija – mala potrošnja Vert.X aplikacije koja je ostaje na ~80MB za 100 i
250 korisnika

Non-Heap memorija – Vert.X aplikacija troši ~40MB (pre ~60MB); SpringBoot i
Dropwizard aplikacije troše kao i ranije

109
deleteAuthor metod (100 i 250 korisnika) 4/8

110
deleteAuthor metod (500 i 750 korisnika) 5/8

Rezultati preformansi za 500 i 750 korisnika:



#Samples - Rang performansi:
1. Vert.X (blaga prednost u odnosu na preostale dve aplikacije)
2. SpringBoot
3. Dropwizard

Std. Dev. - proporcionalno slična za različite brojeve korisnika, ~40% vrednosti
Average polja

Maximum – SpringBoot aplikacija ima najveće vrednosti za 500 i 750 korisnika

111
deleteAuthor metod (500 i 750 korisnika) 6/8

Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika

SpringBoot 40007 1895 2641 34 3052 0.00 784.53 197.9


500
Dropwizard 40120 1902 2733 31 2929 0.00 798.42 197.3

Vert.X 40526 1881 2629 31 2932 0.00 783.02 199.6


SpringBoot 60654 2816 3941 24 4429 0.00 1216.73 199.6 750

Dropwizard 60494 2818 3895 39 4373 0.00 1197.74 199.2


Vert.X 61026 2804 3884 37 4210 0.00 1199.03 200.5

112
deleteAuthor metod (500 i 750 korisnika) 7/8

Potrošnja memorije:

Heap memorija – mala potrošnja Vert.X aplikacije koja opada na ~80MB (commited)
za 500 i 750 korisnika, a upotrebljeno (used) je samo ~40MB za 500 korisnika; used
~64MB za 750 korisnika

Non-Heap memorija – Vert.X aplikacija troši ~40MB (pre ~60MB); SpringBoot i
Dropwizard aplikacije troše kao i ranije

113
deleteAuthor metod (500 i 750 korisnika) 8/8

114
5. ZAKLJUČAK

115
Zaključak 1/3


Izvršeno upoređivanje:

sinhronog modela programiranja sa blocking (Jetty) i non-blocking (Apache
Tomcat) implementacijama Java web servera

asinhronog modela sa non-blocking implementacijama Java web servera (Netty)


Pisanje reaktivnih aplikacija može zahtevati složen pristup → drugačija paradigma
razmišljanja + ručno pisanje SQL upita

ORM framework-ci (npr. Hibernate) olakšavaju posao i štede vreme programeru→ može
negativno uticati na perfromanse

116
Zaključak 2/3

Blocking implementacije koda su bolje za čitanje podataka, ali zahtevaju uređaje sa jakim
konfiguracijama zbog povećeg Memory Footprint-a→ aplikacije sa mnogo HTTP GET zahteva

Non-blocking implementacije su bolje za unos, izmenu i brisanje podataka → razlog za
primenu u mikroservisima za obradu velike količine podataka u realnom vremenu

Problem reaktivnih aplikacija:

pronalazak adekvatne/recenzirane literature i primera (GitHub issues, Stackoverflow,
Google Groups i sl.) → pomoć zajednice i improvizacija

Težak debugging (callback funkcije)

Visok nivo poznavanja asinhronog modela programiranja, tehnika i primenljivih arh.
dizajn obrazaca

Dropwizard framework ima sličan problem sa literaturom i primerima → suprotna situacija kod
SpringBoot framework-a

117
Zaključak 3/3

Slaba pouzdanost dobijenih rezultata zbog izvršavanja Java web
aplikacije, baze podataka (bottleneck) i simulacije korisnika na istom
uređaju

Ideje za dalje istraživanje:

Testiranje na cluster-u računara → realnija postavka i adekvatan uvid u
skaliranje aplikacija

Cloud platforma (npr. AWS, NewRelic, Google Cloud Platform)

Stress testing (npr. Black Friday)

KONAČNO: Potrebe klijenta, raspoloživi resursi (vreme, novac, znanje i
iskustvo programera, hardware)

118
119

You might also like