- Короткая ссылка на этот Гист: https://bit.ly/playtech_kyiv
- Слайды: https://sizovs.net/jninja
- Видео обоих дней уже лежат в Slack
- Kent Beck – создатель eXtreme Programming. У него есть две бомбические книги: "TDD by Example" и "XP Explained."
- Sandi Metz – придумала термин "echo chambers". Написала отличную книгу по ООП "99 Bottles of OOP." Еще стоит посмотреть все ее доклады с конференций.
- Martin Fowler – написал книгу Refactoring и Patterns of Enterprise Application Architecture (PEAA). Вторую следует почитать, если хотите систематизировать знания по классическим паттерам архитектуры enterprise приложений.
- Michael Nygard – написал книгу по нам и принципам создания отказоустойчивых систем "Release It!"
- Егор Бугаенко – написал книгу "Elegant Objects". На мой взгляд первое издание книги – это лучшая книга по ООП, но слегка "религиозная". Нужно фильтровать.
- Growing Object-Oriented Software Guided by Tests – лучшая книга по TDD.
- Domain-Driven Design (Evans) - лучшая книга по DDD (хоть и достаточно тяжелая для восприятия)
Если как и я вы любите читать, то можете найти меня на сайте Goodreads: https://www.goodreads.com/user/show/24705191-eduards-sizovs. Там я даю оценку книгам по программированию (и не только). Все книги с 5 звездами очень крутые.
- SRP
- OCP
- LSP
- ISP
- DIP
По SOLID у меня есть доклад: https://vimeo.com/60193259. Это мой первый публичный доклад, поэтому не судите строго. До сих пор считаю что тему SOLID раскрыл успешно и там есть классные примеры :-)
- InheritableThreadLocal
- ReactiveTransactionManager (Spring)
- Structure 101 - инструмент для управления зависимостями между пакетами
- Code Structural Analysis или видео про принципы управления зависимостями между пакетами: https://www.youtube.com/watch?v=t1bJ3W5Uc_k.
- Keep It Clean или видео про "чистую" архитектуру джава приложений. Так как многих из вас тема архитектуры интересует, то очень советую посмотреть этот доклад. https://www.youtube.com/watch?v=sND1AR7Q_T0
На остальные видео есть ссылки в слайдках.
Список библиотек, которые я упомянул во время тренинга можете найти вот здесь: https://sizovs.net/2020/11/24/java-libraries-i-like. В этом списке есть и другие библиотеки, которые во время тренинга мы не рассматривали. Обязательно пробегитесь по списку. Особенно советую поиграться с библиотекой ArchUnit, которую мы не успели рассмотреть во время тренинга.
Так же мы говорили, что в Скале есть класс Try. Вот популярная Java библиотека, в которой тоже есть класс Try. И еще удобный паттерн матчинг: https://www.vavr.io/
- По этой ссылке лежит приложение, в котором используется библиотека PipelinR вместе со Спрингом: https://github.com/sizovs/unsuck-java. Там еще есть много разных интересных примеров: юнит тесты на Spock, Transactional Outbox (это паттерн когда мы сохраняем команды для дальшейнего исполнения в SQL базе данных, а не в очереди типа ActiveMQ). Там же есть примеры использования jOOQ, Dagger, и еще много чего.
- Паттерн "Printers". Смотрим класс BankStatement, метод print() в демо приложении. Суть в том, что объект сам может себя сериализовать в Json.
- Паттерн "Method Object". Смотрим класс BankStatement и находим вложенные классы EnforceWithdrawalLimits и EnforcePositiveBalance. Этот паттерн помогает сгруппировать приватные методы.
- Доступ к данным без репозиториев. Мы узнали что такое ДАО и что такое репозиторий. Но есть вариант вообще не создавать репозитории, а просто писать файндер-классы (см. класс BankAccountByIban). А для записи в базу использовать DSLContext (jOOQ), SessionFactory (Hibernate), или EntityManager (JPA). Несмотря на то, что мы немного "скауплим" доменную модель с persistence, на практике полноценный декауплинг или невозможен или непрактичен.
В целом покрыли бОльшую часть материала! : ) Вы - молодцы!
- Давайте дружить на Линкедин (https://www.linkedin.com/in/eduardsi) или в Твиттере https://twitter.com/eduardsi.
- Если есть желание, можете выполнить домашку. Вариант 1: https://github.com/sizovs/jninja_challenge Вариант 2: https://github.com/sizovs/awesome-homework-for-java-developers
- Приезжайте в Ригу на ДевТернити https://devternity.com. Это хорошая возможность нахватать новые инсайты, пообщаться с коллегами из Эстонии (они часто приезжают) и просто хорошо провести время. На сегодняшний день, ДевТернити по многим рейтингам считается одной из лучших конференций в Европе.
- Если тренинг понравился, зовите меня еще :-) Всегда рад помочь.
// 1. refactor the code so it's at the same level of abstraction (SLAP).
int from = 8000;
int to = 9000;
IntStream
.rangeClosed(from, to)
.mapToObj(Port::new)
.filter(wrap().function(Port::free)) // NoException
.findFirst();
// 2. Find a missing domain object and reify (thingify) it.
class Company {
private VatNumber vatNumber;
// 3. Find missing domain objects and reify (thingify) them.
borrower.applyForALoan(Application application, ExistingLoans existingLoansByDate);
class Loans {
}
class ExistingLoans {
Loans loans;
}
class Application {
BigDecimal amount,
int term
}
// 4. Find a missing domain objects and reify (thingify) it.
class MortgageRisk {
private final BigDecimal risk;
MortageRisk(MortageApplication mortgageApplication, MortgagePolicy policy) {
this.risk = calculate(mortgageApplication);
}
boolean isTolerable() {
...
}
@Override
boolean equals(@Nullable MortgageRisk obj) {
if (obj == null) {
return false;
}
if (obj instanceof MortgageRisk other) {
return this.risk.equals(other.risk);
}
return false;
...
}
}
// 5. Find a missing domain objects and reify (thingify) it.
class BankruptcyProbabilityCalculator {
BigDecimal calculate(Business business) { ... }
}
class BankruptcyProbabilityUtils {
boolean isHigh(BigDecimal decimal) { ... }
}
// Structure 101
//
ua.playtech.app.business
class Business
ua.playtech.app.insolvency
class BankruptcyProbability
BankruptcyProbability bankruptcyProbability = business.bankruptcyProbability() {
}
class BankruptcyProbability {
BankruptcyProbability(Business business) {
this.probability = ...
}
boolean isHigh() {
}
}
// 6. Replace a procedural design (and an agent noun CsvParser) with an object. Also, make sure the new design doesn't violate CQS (queries = nouns, commands = verbs).
class CsvFile<T extends Line> {
CsvFile(Path location) {
this.location = location;
}
Stream<T> lines() {
// StreamEx
}
}
var csvFile = new CsvFile(...);
csvFile.lines().forEach(...)
// 7. Replace a procedural design (and an agent noun Pinger) with an object
interface Host {
Ping ping(int something);
}
// 8. Replace a procedural design (and an agent noun MoneyFormatter) with an object
interface MoneyFormatter {
String format(Money money);
}
class PrettyMoney {
PrettyMoney(Money) {
}
@Override
public toString() {
return ...;
}
}
class Iso142FormattedMoney {
Iso142FormattedMoney(Money) {
}
@Override
public toString() {
return ...;
}
}
var money = new Money(100.00);
System.out.println("You have: " + new Pretty(money) + ". OK?");
money.format();
money.formatted(Format format);
// 9. Turn XmlMarshaller into a class Xml
// + Make the class generic (decouple it from Invoice)
// + Use type inference
// + Use try-with-resources block
class Xml<T> {
Xml(T entity) {
this.entity = entity;
}
byte[] bytes() {
try (var out = new ByteArrayOutputStream()) {
JaxbMarshaller jaxb = new JaxbMarshaller(entity.getClass());
jaxb.marshallObject(entity, out);
return out.toByteArray();
}
}
}
// 10. /validate/ method is a query (returns the result), but it sounds like an action. Fix it!
interface Input {
boolean isValid();
}
// 11. ditch setters and provide domain-specific commands.
class BankAccount {
enum Status {
CLOSED, OPEN
}
private Status status;
public void open() {
this.status = OPEN;
}
public void close() {
this.status = CLOSED;
}
}
// 12. make Permissions "optional", ditch a setter and provide a domain-specific command.
class User {
private Permissions permissions = Permissions.NONE;
void grant(Permissions permissions) {
this.permissions = permissions
}
void revoke(Permissions permissions) {
this.permissions = Permissions.NONE;
}
}
user.revoke(permissions);
user.grant(permissions);
// 13. Because of CQS, a naming conflict might arise. Fix it! (no getters & setters allowed).
user.currentBan() // returns user's ban and the corresponding information (if any)
user.putABan() // bans a user
// 14. Can you spot an object that pretends as a service? Fix it!
interface Blacklist {
boolean contains(WebsiteVisitor visitor);
}
interface WebsiteVisitor {
String email();
String ipAddress();
}
// 15. Turn this procedure into an object "AuthenticationToken"
class AuthenticationToken {
AuthenticationToken(Credentials credentials) { throws UserDoesNotExistException
}
public asString() {
...
}
}
// 16. fix naming issues
interface Suite {
interface Test {
void print();
boolean hasFailed()
}
void runAndWait();
Collection<Test> tests();
}
suite.runAndWait()
suite.tests().stream().filter(Test::hasFailed).forEach(Test::prettyPrint)
// 17. Can you SLAP (Single Level of Abstraction Principle) it?
boolean destroyButtonAvailable =
widgets
.stream()
.filter(Widget::isButton)
.map(Widget::label)
.anyMatch("Destroy The World"::equals);
// 18.
// implement the /fullName/ method so that it:
// 1. returned "firstName lastName" if nickname is missing
// 2. returned "firstName <nickname> lastName" if nickname is present.
// For example: "Robert Martin" or "Robert <Uncle Bob> Martin"
class User {
private final Optional<String> nickName;
private final String firstName;
private final String lastName;
String fullName() {
var nick = nickName.map(nick -> " <" + nickname + "> ").orElse(" ");
return firstName + nick + lastName;
return nickname
.map(nick -> firstName + " <" + nickname + "> " + lastName)
.orElseGet(() -> firstName + " " + lastName)
}
}
// 19.
// from variable names, omit words that are deductable from the context
void openNewBankAccount() {
var withdrawalLimits = WithdrawalLimits.defaults(env);
var accountHolder = new AccountHolder(...);
var account = new BankAccount(accountHolder, withdrawalLimits);
account.open();
account.deposit(openingBonus());
repository.save(account);
}
var password = new SecurePassword(vault);
password.toString(); // 23424
password.toString(); // 24eqwdara
password.toString();
password.toString();
password.toString();
// 20.
// calling a logic (such as remote system call) in a constructor not always a good idea. Do you know how to fix that?
class SecurePassword {
private final ConcurrentHashMap<Vault, String> cache = new ConcurrentHashMap(1, 1.0f);
private SecurePassword(Vault vault) {
this.vault = vault;
}
@Override
public String toString() {
return cache.computeIfAbsent(vault, vault::verySecurePassword);
}
}
// 20.
// calling a logic (such as remote system call) in a constructor not always a good idea. Do you know how to fix that?
class SecurePassword {
private final Supplier<String> lazyPassword;
private SecurePassword(Vault vault) {
// Caffeine
this.lazyPassword = memoize(vault::verySecurePassword);
}
@Override
public String toString() {
return lazyPassword.get();
}
}