Professional Documents
Culture Documents
MasterRad Prezentacija NikolaStevanovic412m-16 16x9 Ver2 PRESENTED
MasterRad Prezentacija NikolaStevanovic412m-16 16x9 Ver2 PRESENTED
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
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
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 "
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");
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)
);
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);
@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;
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());
54
Implementacija Vert.X aplikacije 9/18
●
crossJoin metod se koristi da se kreira Dekartov proizvod
book_id i kolekcije author_id elemenata
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()));
}
};
}
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("\\\\", "");
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!")));
}
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;
64
3. TESTIRANJE
65
Testiranje
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
{ {
"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
72
getSingleBook metod (100 i 250 korisnika) 1/8
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
74
getSingleBook metod (100 i 250 korisnika) 3/8
75
getSingleBook metod (100 i 250 korisnika) 4/8
76
getSingleBook metod (500 i 750 korisnika) 5/8
77
getSingleBook metod (500 i 750 korisnika) 6/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
78
getSingleBook metod (500 i 750 korisnika) 7/8
79
getSingleBook metod (500 i 750 korisnika) 8/8
80
getAllBooks metod (100 i 250 korisnika) 1/8
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
82
getAllBooks metod (100 i 250 korisnika) 3/8
83
getAllBooks metod (100 i 250 korisnika) 4/8
84
getAllBooks metod (500 i 750 korisnika) 5/8
85
getAllBooks metod (500 i 750 korisnika) 6/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
86
getAllBooks metod (500 i 750 korisnika) 7/8
87
getAllBooks metod (500 i 750 korisnika) 8/8
88
getAllOrders metod (100 i 250 korisnika) 1/10
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
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
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
SpringBoot 105289 1616 1910 214 2124 0.00 369.90 348.2 750
95
getAllOrders metod (500 i 750 korisnika) 8/10
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
99
createOrder metod (100 i 250 korisnika) 2/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
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
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
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
107
deleteAuthor metod (100 i 250 korisnika) 2/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
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
111
deleteAuthor metod (500 i 750 korisnika) 6/8
Aplikacija #Samples Average 95% Min Maximum Error % Std. Dev. Throughput Br.
kb/s korisnika
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