Developer une application web en Java avec des technologies et outils simples, et si c'était une idée pleine de promesses ? Loin des frameworks ORM, des frameworks d'injection de dépendances (DI), des frameworks web, des outils de build compliqués, à quoi ressemble le développement web en Java en revenant à la simplicité ? Quels en sont les avantages et que faut-il être prêt à sacrifier ?
Inspiré par une discussion dans un groupe de discussion sur le développement piloté par le tests, j'ai cherché à répondre à cette question. Pour ce faire, j'ai réécris une application à l'origine basé sur Spring, Spring MVC, Hibernate et Maven en utilisant cette fois Simple Web (exit la spec Servlet et Spring MVC), JDBC (exit les ORMs), Buildr (exit Maven) et en laissant faire les frameworks d'injection de dépendances (exit Spring). Le tout évidemment complètement piloté par les tests, comme à mon habitude. Le moins que je puisse dire, c'est que la version simple me plait beaucoup plus que la version d'origine.
Cette session sera l'occasion de partager ce que j'ai appris de cette expérience et d'échanger sur les alternatives aux mondes des frameworks. Nous aborderons les points suivants :
Se passer d'un framework d'injection de dépendances
Clean code et JDBC
Se libérer de la spec Servlet
RESTful routing comme des grands
Le monde sans Maven
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Retour à la simplicité
1. Retour à la simplicité
Vincent Tencé
@testinfected
http://vtence.com
http://github.com/testinfected
2. Un sentiment de déja-vu ?
Vous démarrez un projet avec une dizaine de librairies et frameworks
Le bagage à trainer est lourd et vous coute cher
Un des frameworks se met en travers de votre chemin
Vous vous sentez prisonnier d’un des frameworks
Un framework vous surprend par sa « magie »
3.
4.
5. Le point de départ
• Spring et Spring MVC
• Velocity et SiteMesh
• Hibernate, JPA
• Hibernate Validator
• Maven
• 53 jars externes !
6.
7. Le défi
• Uniquement des outils simples
• DIYS (Do It Yourself Simply)
• Assemblage, déploiement et configuration faciles
• XML
• Annotations
• Framework (Web, ORM, DI)
8.
9.
10.
11. Build
define 'petstore', [..] do
define 'domain' do
compile.with
test.with HAMCREST
package :jar
end
define 'persistence' do
compile.with project(:domain)
test.with HAMCREST, :flyway, :mysql, NO_LOG, [...]
package :jar
end
define 'webapp' domain
compile.with :simpleframework, :jmustache, [...]
test.with HAMCREST, :antlr_runtime, :cssselectors, :hamcrest_dom, [...]
test.with transitive(artifacts(:nekohtml, :htmlunit, :jmock_legacy))
package :jar
end
12. Injection de dépendances
AttachmentStorage attachments = new FileSystemPhotoStore("/photos");
Connection connection = new ConnectionReference(request).get();
Transactor transactor = new JDBCTransactor(connection);
ProductCatalog products = new ProductsDatabase(connection);
ItemInventory items = new ItemsDatabase(connection);
OrderBook orders = new OrdersDatabase(connection);
ProcurementRequestHandler procurement =
new PurchasingAgent(products, items, transactor)
OrderNumberSequence orderNumbers = new OrderNumberDatabaseSequence(connection);
Cashier cashier = new Cashier(orderNumbers, orders, transactor);
Messages messages =
new BundledMessages(ResourceBundle.getBundle("ValidationMessages"))
14. MVC
public class ShowOrder implements Application {
private final OrderBook orderBook;
private final Page orderPage;
public ShowOrder(OrderBook orderBook, Page orderPage) {
this.orderBook = orderBook;
this.orderPage = orderPage;
}
public void handle(Request request, Response response) throws Exception {
String number = request.parameter("number");
Order order = orderBook.find(new OrderNumber(number));
orderPage.render(response, context().with("order", order).asMap());
}
}
15. Accès aux données
public class OrdersDatabase implements OrderBook {
private final Connection connection;
[...]
public OrdersDatabase(Connection connection) {
this.connection = connection;
}
private List<LineItem> findLineItemsOf(Order order) {
return Select.from(lineItems).
where("order_id = ?", idOf(order).get()).
orderBy("order_line").
list(connection);
}
private Order findOrder(OrderNumber orderNumber) {
return Select.from(orders, "_order").
leftJoin(payments, "payment", "_order.payment_id = payment.id").
where("_order.number = ?", orderNumber).
first(connection);
}
16. Transactions
public class Cashier implements SalesAssistant {
[...]
public OrderNumber placeOrder(PaymentMethod paymentMethod) throws Exception {
Ensure.valid(paymentMethod);
QueryUnitOfWork<OrderNumber> order = new QueryUnitOfWork<OrderNumber>() {
public OrderNumber query() throws Exception {
OrderNumber nextNumber = orderNumberSequence.nextOrderNumber();
final Order order = new Order(nextNumber);
order.addItemsFrom(cart);
order.pay(paymentMethod);
orderBook.record(order);
cart.clear();
return nextNumber;
}
};
return transactor.performQuery(order);
}
17. Contraintes de validité
public class CreditCardDetails extends PaymentMethod implements Serializable {
private
private
private
private
final
final
final
final
CreditCardType cardType;
Constraint<String> cardNumber;
NotNull<String> cardExpiryDate;
Valid<Address> billingAddress;
public CreditCardDetails(CreditCardType type,
String number,
String expiryDate,
Address billingAddress) {
this.cardType = type;
this.cardNumber = Validates.both(Validates.notEmpty(number),
Validates.correctnessOf(type, number));
this.cardExpiryDate = Validates.notNull(expiryDate);
this.billingAddress = Validates.validityOf(billingAddress);
}
18. Validation
public class Validator {
public <T> Set<ConstraintViolation<?>> validate(T target) {
Valid<T> valid = Validates.validityOf(target);
valid.disableRootViolation();
ViolationsReport report = new ViolationsReport();
valid.check(Path.root(target), report);
return report.violations();
}
[...]
}
19. Formulaires
public class PaymentForm extends Form {
public static PaymentForm parse(Request request) {
return new PaymentForm(new CreditCardDetails(
valueOf(request.parameter("card-type")),
request.parameter("card-number"),
request.parameter("expiry-date"),
new Address(request.parameter("first-name"),
request.parameter("last-name"),
request.parameter("email"))));
}
private final Valid<CreditCardDetails> paymentDetails;
public PaymentForm(CreditCardDetails paymentDetails) {
this.paymentDetails = Validates.validityOf(paymentDetails);
}
public CreditCardType cardType() { return paymentDetails().getCardType(); }
public CreditCardDetails paymentDetails() { return paymentDetails.get(); }
}
20. Toutefois
• Pas très « entreprise »
• Pas à la portée de toutes les équipes ?
• Pas à toutes les sauces
• Pas sans risque ?
21. Leçons apprises
• Éviter la tentation des frameworks
• Utiliser des outils simples, légers et spécialisés
• Construire mes propres outils
• S’inspirer des meilleures idées; réécrire le code simplement
• Spécialiser plutôt que de généraliser
22. Références
• Nouvelle version :
https://github.com/testinfected/simple-petstore
• Ancienne version :
https://github.com/testinfected/petstore
• Data Mapping :
https://github.com/testinfected/tape