Retour à la simplicité

303 views

Published on

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

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Retour à la simplicité

  1. 1. Retour à la simplicité Vincent Tencé @testinfected http://vtence.com http://github.com/testinfected
  2. 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. 3. Le point de départ • Spring et Spring MVC • Velocity et SiteMesh • Hibernate, JPA • Hibernate Validator • Maven • 53 jars externes !
  4. 4. Le défi • Uniquement des outils simples • DIYS (Do It Yourself Simply) • Assemblage, déploiement et configuration faciles • XML • Annotations • Framework (Web, ORM, DI)
  5. 5. 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
  6. 6. 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"))
  7. 7. Routing Router router = Router.draw(new DynamicRoutes() {{ get("/products").to(new ListProducts(products, attachments, pages.products())); post("/products").to(new CreateProduct(procurement)); get("/products/:product/items").to(new ListItems(items, pages.items())); post("/products/:product/items").to(new CreateItem(procurement)); get("/cart").to(new ShowCart(cashier, pages.cart())); post("/cart").to(new CreateCartItem(cashier)); get("/orders/new").to(new Checkout(cashier, pages.checkout())); get("/orders/:number").to(new ShowOrder(orders, pages.order())); post("/orders").to(new PlaceOrder(cashier)); delete("/logout").to(new Logout()); map("/").to(new StaticPage(pages.home())); }});
  8. 8. 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()); } }
  9. 9. 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); }
  10. 10. 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); }
  11. 11. 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); }
  12. 12. 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(); } [...] }
  13. 13. 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(); } }
  14. 14. Toutefois • Pas très « entreprise » • Pas à la portée de toutes les équipes ? • Pas à toutes les sauces • Pas sans risque ?
  15. 15. 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
  16. 16. 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

×