testing, testing, testing

In his speech to the party at Labour Pary conference back in October 1996, the leader Tony Blair  said: “Ask me my three main priorities for government, and I tell you: education, education, education.”

Ask me my three main priorities for software development, and I tell you: “testing, testing, testing.”

Back in the day, I tended to use JUnit as a quick and dirty way of doing a proof of concept, testing some Java method would work or experimenting with libraries I hadn’t used before. That all changed when I read Practical Unit Testing with JUnit and Mockito by Tomek Kaczanowski.

On page one of his book, he quotes Micheal Feather author of ‘Working Effectively With Legacy Code (2004)’

Code without tests is bad code. It doesn’t matter how well written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behaviour of our code quickly and verifiably (sic). Without them, we really don’t know if our code is getting better or worse.

In the spring of 2002, I had a contract with the English Football Association working on their website for the FIFA World Cup taking place in Japan and South Korea.  I was writing the server side code to access the database and return the data as objects to the bea Weblogic Portal they were using. We appeared to operate in a blame culture, where if any part of the website didn’t work you blamed the other team. So the UI team were constantly claiming that fault was with the server side people when the umpteenth article about David Beckham or Sven didn’t display.

I soon got tired of this and I decided to implement my own “testing framework”, I use the word loosely.  I was using JBuilder as my IDE at the time and JUnit only appeared in Eclipse in November 2002.

So I wrote a series of Java Server Pages (JSPs) for every TLD (Tag Library Descriptor) that made a call to a method in my backing bean which then called my EJB’s method and rendered the result in the browser. Though it didn’t look pretty it proved a point and I was no longer the subject of pointing fingers from the UI team.

So when they tweaked the database or added new content I’d load my dozens of JSP pages in the browser and check my ‘legacy” code was still working.

By 2003 I’d switched to Eclipse and was running unit tests (JUnit 3) though I wasn’t big on assertions.

It wasn’t until the mid-noughties when I went for an interview at BP in London I realised the power of test-driven development (TDD) and how I was missing out. The first thing they said to me as I entered the room, well, in fact, the interviewer shouted it at me; “Are you test aware?”. I thought he was on some evangelical mission and frankly a bit weird. The shocked look on my face said it all and I blatantly wasn’t. I didn’t get the contract.

Reflecting on what had been said during the interview on the train home I thought maybe there was something in test-driven development, though that was the first time I’d experienced a company using it on that scale.

It wasn’t until a short piece of ‘consultancy’ at home furnishings retailer Dunelm, actually running lots of static code analyser tools on the Java code written by a third-party they had engaged and writing reports to accompany the tool’s output, did I see the real-world success of test-driven development (TDD).

Dunelm released a new version of the website into production nearly every day, sometimes multiple times. They were big users of continuous integration with Jenkins. Their Jenkins job first checked out the latest release of the code base from Git and then ran various static code analysis tools, followed the hundreds of JUnit tests. The progress of these test could be monitored by everyone in the department as they were displayed in real-time, on a huge wall mounted 100+ inch screen at one end of the office. The progress bar gradually changed from amber to green as each test passed. If one failed the whole bar would go to red and they would roll back to the previous master code branch which had passed every one of the tests and deployment of the new release would be abandoned.

The flip side of that occurred when I recently worked at another major online B2B retailer a couple of years ago who was going to attempt to upgrade their entire website, deployed on JBoss 4.0 (released in 2004) to JBoss WildFly 10.0 (2016). They were hoping to move through twelve years of innovation and major changes to the new architecture of (JBoss) WildFly in three months. Not doing it gradually, but with the ‘big bang’ approach.

As Eclipse was the defacto standard IDE at this place, they could have used JUnit integration and been writing unit tests since the original J2EE project started in 2004. From seeing Confluence based documentation (retro) added from the period and talking to Architects, they had decided it was too much effort and would “slow development down” and “any bugs would be caught in system and user acceptance testing”. Whilst I was there a senior developer was assigned the task of investigating the writing of retrospective unit test. I think she found the sheer number required overwhelming and though they had lots of meetings to discuss, nothing was decided. She went off on maternity leave and I left the month she came back.

The (very optimistic) estimated three months migration would involve almost all of the entire development department. During the ‘three months’ everything in all the Sprint backlogs was frozen and no new stories or tasks were added. The live website was not to be updated, no matter how important or serious the issue. As the sheer size of the tasks was realised no one was allowed to communicate with any of the upgrade teams and they were all moved from their recently refurbished spacious open plan offices to an old office with no outside windows and attached to a warehouse. They couldn’t come out until it was completed!

The migration was complicated and they had planned to move through each iteration of the application server upgrading all third-party libraries, the JVM, version of Oracle etc. for each major JBoss release number. Only when they had tested it would they move onto the next version, until they finally arrived at version 10 of WildFly running on Java EE7 with the latest release of Oracle and all the external third-party libraries they used upgraded as well.

Testing involved sitting down at a two PCs one with the current live version of the website and the other PC with the test release. They would then do the same product searches or purchasing sequences to see that they both behaved the same and a DBA queried the Oracle back-end to check that the same data had been persisted. Laborious and not exactly scientific, open to human error.

The man/women power and time invested was huge and overran by many months and bugs were still cropping up a year later. There’s no doubt if they had started writing those unit tests prior to the migration commencing and continued writing them in tandem, the whole process would have been a lot quicker, a lot smoother and the outcome would have been more positive. It would also have given them a huge collection of unit tests to run on the next and future upgrades. They also could have incorporated all these tests within their already in place, Jenkins environment which they used solely for their very complex ANT build process.

My experience shows that on a greenfield project implement Test Driven Development and in an existing long-term project, retrofit unit tests.

Tony Blair was in power for ten years, I doubt if many of the websites I’ve worked on in the past will be around for ten years. When they add those new ‘cool’ features or fix an existing bugs it will crash and be down for days if not weeks (TSB May 2018) and consumers will go elsewhere.

One way to stop this is testing, testing, testing. Preferably automated with a 150+ inch screen OLED 4K TV at one end of the office. Least the TV tech will have been upgraded, tested and working.

ORM for NoSQL (part II)

There are plenty of sites explaining the virtues and advantages of normalization versus denormalization of data in the design of a NoSQL database. So there’s no need for me to “cut ‘n’ paste” them here.

However, I chose to denormalize my MySQL database when migrating  it to MongoDB mainly for the following two reasons (from AWS: Should Your DynamoDB Table Be Normalized or Denormalized?):

  • You need to store small items, with few attributes.  For reads, the item size should not exceed 4 KB.  (A read capacity unit (RCU) is defined as one read per second, for an item up to 4 KB in size.)  For writes, the item size should not exceed 1 KB (the size of one WCU).
  • Your applications need to read and write data in a high-traffic environment, without being concerned about consistency and synchronization of data across multiple tables.

The following JavaScript creates my MongoDB collection and inserts one document.

collectionFoodsCosmeticsMedicines.insertOne(
    {
        // _id MongoDB default PK.
        //_id: "",
        "ean": "05052319711639",
        "gtin": "05052319711639",
        "tpnb": "051627961",
        "tpnc": "258290127",
        "description": "Tesco Everyday Valuebaked Beans In Tomato Sauce 420G",
        "brand": "TESCO VALUE",
        "supermarket": "Tesco",

        "product_name": "product_name",
        "quantity_grammes": "420.0",
        "portion_serving_sachet_item_amount": "",
        "live": "yes",
        "timestamp_lastupdated": lastupdated,

        "qtyContents": [
            {
                "quantity": "420.0",
                "totalQuantity": "420.0",
                "quantityUom": "g",
                "netContents": "420g e",
                "avgMeasure": ""
            }
        ],

        "productCharacteristics": {
            "isFood": "yes",
            "isDrink": "no",
            "healthScore": "10",
            "isHazardous": "no",
            "storageType": "dry"
        },


        // Arrays of data
        "ingredients": [
            "Haricot Beans (44%) (Water, Haricot Beans)",
            "Tomato Puree (27%) (Water, Tomato Purée)",
            "Water",
            "Sugar",
            "Glucose-Fructose Syrup",
            "Modified Maize Starch",
            "Salt",
            "Onion Powder",
            "Paprika",
            "Maltodextrin",
            "Spice Extracts (Paprika Extract, Clove Extract, Capsicum Extract)",
            "Flavouring"
        ],

        "calcNutrition": {
            "per100Header": "100g contains",
            "perServingHeader": "Half of a can (210g) contains",

            calcNutrients: [
                {
                    "name": "Energy (kJ)",
                    "valuePer100": "364.8",
                    "valuePerServing": "766.08"
                },
                {
                    "name": "Energy (kcal)",
                    "valuePer100": "364.80kj86.5",
                    "valuePerServing": "766.08kj181.65"
                },
                {
                    "name": "Fat (g)",
                    "valuePer100": ".5",
                    "valuePerServing": "1.05"
                },
                {
                    "name": "Saturates (g)",
                    "valuePer100": ".14",
                    "valuePerServing": ".29"
                },
                {
                    "name": "Carbohydrate (g)",
                    "valuePer100": "14.6",
                    "valuePerServing": "30.66"
                },
                {
                    "name": "Sugars (g)",
                    "valuePer100": "4.5",
                    "valuePerServing": "9.45"
                },
                {
                    "name": "Fibre (g)",
                    "valuePer100": "4.4",
                    "valuePerServing": "9.24"
                },
                {
                    "name": "Protein (g)",
                    "valuePer100": "3.7",
                    "valuePerServing": "7.77"
                },
                {
                    "name": "Salt (g)",
                    "valuePer100": ".55",
                    "valuePerServing": "1.15"
                }
            ]
        },

        // Arrays of data
        "nutrients": {
            "nutrient_name": "d"
        },

        // Arrays of data
        "e_numbers": {
            "e_number_name": "e"
        },

        // Arrays of data
        "chemicals_minerals_vitamins": {
            "chemical_mineral_vitamin_name": "f"
        },

        //
        "lifestyle": {
            "name": "Lifestyle",
            "value": "Suitable for Vegetarians"
        }
    }
);

Using MongoDB Compass I can see my Collection ‘foodsCosmeticsMedicines’ is < 4K:

I create the Entity Bean to represent the ‘foodsCosmeticsMedicines’ MongoDB collection:

import com.google.gson.annotations.Expose;
import org.hibernate.annotations.Type;
import org.hibernate.search.annotations.Indexed;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Entity(name = "FoodsCosmeticsMedicines")
@Indexed
@Table(name = "foodsCosmeticsMedicines")
public class FoodsCosmeticsMedicines implements Serializable {

    // Arrays of Objects
    @Expose(deserialize = true, serialize = true)
    @Embedded
    ProductCharacteristics productCharacteristics;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    CalcNutrition calcNutrition;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    Nutrients nutrients;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    Enumbers enumbers;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    ChemicalsMineralsVitamins chemicalsMineralsVitamins;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    Lifestyle lifestyle;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Type(type = "objectid")
    private String id;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "ean")
    private String ean;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "gtin")
    private String gtin;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "tpnb")
    private String tpnb;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "tpnc")
    private String tpnc;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "description")
    private String description;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "brand")
    private String brand;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "supermarket")
    private String supermarket;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "product_name")
    private String product_name;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "quantity_grammes")
    private String quantity_grammes;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "portion_serving_sachet_item_amount")
    private String portion_serving_sachet_item_amount;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "timestamp_lastupdated")
    private Date timestamp_lastupdated;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @ElementCollection
    private List<QtyContents> qtyContents = new ArrayList<QtyContents>();
    @Expose(deserialize = true, serialize = true)
    @ElementCollection
    private List<Ingredients> ingredients = new ArrayList<Ingredients>();

    /**
     *
     */
    public FoodsCosmeticsMedicines() {
    }

    /**
     *
     * @param id String
     */
    public FoodsCosmeticsMedicines(String id) {
        this.id = id;
    }

    /**
     *
     * @return String
     */
    public String getId() {
        return this.id;
    }

    /**
     *
     * @param id String
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     *
     * @return String
     */
    public String getEan() {
        return this.ean;
    }

    /**
     *
     * @param ean String
     */
    public void setEan(String ean) {
        this.ean = ean;
    }

    /**
     *
     * @return String
     */
    public String getGtin() {
        return this.gtin;
    }

    /**
     *
     * @param gtin String
     */
    public void setGtin(String gtin) {
        this.gtin = gtin;
    }

    /**
     *
     * @return String
     */
    public String getTpnb() {
        return this.tpnb;
    }

    /**
     *
     * @param tpnb String
     */
    public void setTpnb(String tpnb) {
        this.tpnb = tpnb;
    }

    /**
     *
     * @return String
     */
    public String getTpnc() {
        return this.tpnc;
    }

    /**
     *
     * @param tpnc String
     */
    public void setTpnc(String tpnc) {
        this.tpnc = tpnc;
    }

    /**
     *
     * @return String
     */
    public String getDescription() {
        return this.description;
    }

    /**
     *
     * @param description String
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     *
     * @return String
     */
    public String getBrand() {
        return this.brand;
    }

    /**
     *
     * @param brand String
     */
    public void setBrand(String brand) {
        this.brand = brand;
    }

    /**
     *
     * @return String
     */
    public String getSupermarket() {
        return this.supermarket;
    }

    /**
     *
     * @param supermarket String
     */
    public void setSupermarket(String supermarket) {
        this.supermarket = supermarket;
    }

    /**
     *
     * @return String
     */
    public String getProduct_name() {
        return this.product_name;
    }

    /**
     *
     * @param product_name String
     */
    public void setProduct_name(String product_name) {
        this.product_name = product_name;
    }

    /**
     *
     * @return String
     */
    public String getPortion_serving_sachet_item_amount() {
        return this.portion_serving_sachet_item_amount;
    }

    /**
     *
     * @param portion_serving_sachet_item_amount String
     */
    public void setPortion_serving_sachet_item_amount(String portion_serving_sachet_item_amount) {
        this.portion_serving_sachet_item_amount = portion_serving_sachet_item_amount;
    }

    /**
     *
     * @return Date
     */
    public Date getTimestamp_lastupdated() {
        return this.timestamp_lastupdated;
    }

    /**
     *
     * @param timestamp_lastupdated Date
     */
    public void setTimestamp_lastupdated(Date timestamp_lastupdated) {
        this.timestamp_lastupdated = timestamp_lastupdated;
    }

    /**
     *
     * @return String
     */
    public String getQuantity_grammes() {
        return this.quantity_grammes;
    }

    /**
     *
     * @param quantity_grammes String
     */
    public void setQuantity_grammes(String quantity_grammes) {
        this.quantity_grammes = quantity_grammes;
    }

    /**
     *
     * @return String
     */
    public ProductCharacteristics getProductCharacteristics() {
        return this.productCharacteristics;
    }

    /**
     *
     * @param productCharacteristics ProductCharacteristics
     */
    public void setProductCharacteristics(ProductCharacteristics productCharacteristics) {
        this.productCharacteristics = productCharacteristics;
    }

    /**
     *
     * @return Ingredients
     */
    public List<Ingredients> getIngredients() {
        return this.ingredients;
    }

    /**
     *
     * @param ingredients List<Ingredients>
     */
    public void setIngredients(List<Ingredients> ingredients) {
        this.ingredients = ingredients;
    }

    /**
     *
     * @return CalcNutrition
     */
    public CalcNutrition getCalcNutrition() {
        return this.calcNutrition;
    }

    /**
     *
     * @param calcNutrition CalcNutrition
     */
    public void setCalcNutrition(CalcNutrition calcNutrition) {
        this.calcNutrition = calcNutrition;
    }

    /**
     *
     * @return Nutrients
     */
    public Nutrients getNutrients() {
        return this.nutrients;
    }

    /**
     *
     * @param nutrients Nutrients
     */
    public void setNutrients(Nutrients nutrients) {
        this.nutrients = nutrients;
    }

    /**
     *
     * @return Enumbers
     */
    public Enumbers getEnumbers() {
        return this.enumbers;
    }

    /**
     *
     * @param enumbers Enumbers
     */
    public void  setEnumbers(Enumbers enumbers) {
        this.enumbers = enumbers;
    }

    /**
     *
     * @return ChemicalsMineralsVitamins
     */
    public ChemicalsMineralsVitamins getChemicalsMineralsVitamins() {
        return this.chemicalsMineralsVitamins;
    }

    /**
     *
     * @param chemicalsMineralsVitamins ChemicalsMineralsVitamins
     */
    public void setChemicalsMineralsVitamins(ChemicalsMineralsVitamins chemicalsMineralsVitamins) {
        this.chemicalsMineralsVitamins = chemicalsMineralsVitamins;
    }

    /**
     *
     * @return Lifestyle
     */
    public Lifestyle getLifestyle() {
        return this.lifestyle;
    }

    /**
     *
     * @param lifestyle Lifestyle
     */
    public void setLifestyle(Lifestyle lifestyle) {
        this.lifestyle = lifestyle;
    }

    /**
     *
     * @return List<QtyContents>
     */
    public List<QtyContents> getQtyContents() {
        return this.qtyContents;
    }

    /**
     *
     * @param qtyContents List<QtyContents>
     */
    public void setQtyContents(List<QtyContents> qtyContents) {
        this.qtyContents = qtyContents;
    }

    /**
     *
     * @param o Object
     * @return boolean
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FoodsCosmeticsMedicines)) return false;
        FoodsCosmeticsMedicines that = (FoodsCosmeticsMedicines) o;
        return Objects.equals(this.getId(), that.getId());
    }

    /**
     *
     * @return int
     */
    @Override
    public int hashCode() {

        return Objects.hash(this.getId());
    }

    /**
     *
     * @return String
     */
    @Override
    public String toString() {
        return "FoodsCosmeticsMedicines{" +
                "productCharacteristics=" + productCharacteristics +
                ", calcNutrition=" + calcNutrition +
                ", nutrients=" + nutrients +
                ", enumbers=" + enumbers +
                ", chemicalsMineralsVitamins=" + chemicalsMineralsVitamins +
                ", lifestyle=" + lifestyle +
                ", id='" + id + '\'' +
                ", ean='" + ean + '\'' +
                ", gtin='" + gtin + '\'' +
                ", tpnb='" + tpnb + '\'' +
                ", tpnc='" + tpnc + '\'' +
                ", description='" + description + '\'' +
                ", brand='" + brand + '\'' +
                ", supermarket='" + supermarket + '\'' +
                ", product_name='" + product_name + '\'' +
                ", quantity_grammes='" + quantity_grammes + '\'' +
                ", portion_serving_sachet_item_amount='" + portion_serving_sachet_item_amount + '\'' +
                ", timestamp_lastupdated=" + timestamp_lastupdated +
                ", qtyContents=" + qtyContents +
                ", ingredients=" + ingredients +
                '}';
    }
}

The following method in my EJB (3.2) queries the MongoDB collection ‘FoodsCosmeticsMedicines’

/**
 * @return List<FoodsCosmeticsMedicines>
 */
public List<FoodsCosmeticsMedicines> getAllFoodsCosmeticsMedicines() {
    String json = null;

    Query query = mongoDBEntityManager.createQuery("FROM FoodsCosmeticsMedicines");
    List<com.notifywell.entity.FoodsCosmeticsMedicines> foodsCosmeticsMedicinesList = query.getResultList();
    logger.info(">>>>> getAllFoodsCosmeticsMedicines foodsCosmeticsMedicinesList = {}", foodsCosmeticsMedicinesList.size());

    for (com.notifywell.entity.FoodsCosmeticsMedicines foodsCosmeticsMedicines : foodsCosmeticsMedicinesList) {
        logger.info(">>>>> foodsCosmeticsMedicines id = {}", foodsCosmeticsMedicines.getId());
        logger.info(">>>>> foodsCosmeticsMedicines ean = {}", foodsCosmeticsMedicines.getEan());
        logger.info(">>>>> foodsCosmeticsMedicines description = {}", foodsCosmeticsMedicines.getDescription());

        for (com.notifywell.entity.Ingredients ingredients : foodsCosmeticsMedicines.getIngredients()) {
            logger.info(">>>>> foodsCosmeticsMedicines ingredients = {}", ingredients.getIngredientName());
        }
    }

    return foodsCosmeticsMedicinesList;
}

When I call my method I see in the console all the products (1) with a Bar Code of ‘05052319711639′ and the 9 ingredients. 

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> getAllFoodsCosmeticsMedicines foodsCosmeticsMedicinesList = 1

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines id = 5ae7fc9ec0a118606e42667e

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ean = 05052319711639

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines description = Tesco Everyday Valuebaked Beans In Tomato Sauce 420G

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Haricot Beans (44%) (Water, Haricot Beans)

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Tomato Puree (27%) (Water, Tomato Purée)

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Water

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Sugar

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Glucose-Fructose Syrup

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Modified Maize Starch

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Salt

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Onion Powder

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Paprika

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Maltodextrin

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Spice Extracts (Paprika Extract, Clove Extract, Capsicum Extract)

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Flavouring

The above example shows how easy it is to add JPA ORM to a NoSQL database.

See the following links for installing Hibernate OGM.

Hibernate OGM FAQ and the Hibernate OGM 5.3.1.Final: Reference Guide.

 

Why I hate Pair Programming

The quote American author Harper Lee (To Kill a Mockingbird)

You can choose your friends but you sho’ can’t choose who you pair program with

Whilst she actually said  “your family”, you get my drift.

The rules of Pair Programming were devised in the early noughties and haven’t been updated since 2009. According to the Extreme Programming Organisation rules.

All code to be sent into production is created by two people working together at a single computer

It’s a bit like when you’re out cycling or walking in a group, you always drop down to the lowest common denominator and go at the speed of the slowest person n the group. Paired programming is just like this.

They also say:

Pair programming is a social skill that takes time to learn.

Maybe it does,  but when you don’t like someone you don’t want to spend months sitting next to them for eight hours a day hoping eventually, after six months you’ll get on.

I recently read articles by Patrick Brown The Only Person I’ll Pair Program with is my Cat and Wesley Moore Pair Programming

I agree wholeheartedly with both articles and recommend you read them. As I no longer have a cat, I’m unsure who I’d only pair program with.

A few (of the many) issues I’ve experienced, in my (brief) exposure to paired programming, have been personal hygiene (my pair not me), colour choice and size of the font and other preferences/settings of the IDE and as mentioned in both articles, tiredness and fatigue.

Doing anything intensely, without a break for two, four-hour stints, assuming you get a break for lunch is very tiring.

That’s why I want to break-up my day with the occasional trip 5 minute trip to the BBC News, The Register, Motorcycle News and the Manchester CITY FC sites. Horrors of horrors I could end up pair programming with a United fan bleating on about how back 2011 it was ’35 years without a trophy’.

Whoever introduces Paired Programming into an organisation has obviously never programmed seriously

ORM for NoSQL (part I)

Whilst using the MongoDB JDBC driver to connect to MongoDB and read and persist to, collections and documents is fairly simple, I do find it takes me back to the old days of Java database access of having to write a lot of code, to do not very much.

When I moved to EJB 2.0 in the early noughties, I used Container Managed Persistence (CMP) as the persistence solution. This was the precursor to the Java Persistence API.

Before the days of Open Source and the GNU license, with free integrated development environments (IDE) like Eclipse and IntelliJ community edition,  you had to pay for your  IDE. Borland was one of the leading software development tool providers of the eighties to the early noughties and had an excellent product for Java Development called JBuilder.

Borland’s JBuilder Professional version included a CMP generator tool, which connected to your SQL database and automatically generated the Java code and the tons of XML to map the relationships between tables. It worked perfectly and reduced days of tedious error-prone Java coding and XML writing to hours. Producing code that worked first-time!

The only problem JBuilder Professional cost £3,000 plus VAT. Even in the heady days of the ‘Dot Com bubble’ that was a week’s fees and I was loathed to pay for my own dev tools. Especially when even the most ridiculous idea was being funded with millions of pounds of venture capital. Also, this was pre IR35 and you didn’t have to prove to anyone you were a legitimate business by using your own laptop and development tools.

Fortunately, my wife was working at a local university at the time and by getting her to purchase it and have it delivered to her place of work I was able to get an Educational discounted version for £895 including VAT. One of best £760 odd  I’ve spent. I didn’t advertise the fact I used it and soon became the go-to person for CMP  and built a reputation for being able to deliver bug-free EJB database access in an afternoon. This resulted in many renewals.

In 2006 the Java Persistence API specification was released and JPA replaced CMP  and I discovered Hibernate which has become my defacto ORM provider since.

The creation of Entity Beans from databases is now a common tool in most IDEs and they create all the Hibernate mapping annotations with no or very little hand-coding.

Roll on to 2018. After a few weeks of hand-coding MongoDB access, I started to investigate if there was an ORM was available forMongoDB and I discovered Hibernate’s Object/Grid Mapper (OGM) for NoSQL databases Your NoSQL datastores. One API.

OGM works perfectly and though I had to hand-code my entities it has significantly reduced the amount of code needed and the ease which I can query and persist my MongoDB documents to collections.

All it needs is a little ‘push’

A common requirement of a modern ‘reactive’ application is to refresh a web page when an event has occurred. Not necessarily one instigated by a user clicking on ‘something’, one independent of their actions. In effect ‘push’ a notification back to the web page, be this from an EJB or POJO.

In Java Server Faces (JSF) of which JSF 2.3 is the standard MVC web framework in EE8 this is possible very easily using the ‘socket’ tag from the OmniFaces JSF library.

To trigger a push from the EAR/EJB side to an application scoped push socket, you can make use of CDI events. First, create a custom bean class representing the push event something like the PushEvent class below taking whatever you’d like to pass  as the push message.

 public final class PushEvent {

    private final String message;

    public PushEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

In my EJB, a Stateless Session Bean I use:

beanManager.fireEvent(new PushEvent(foodsCosmeticsMedicines.getBrand()));

to fire the CDI event.

public void onFoodsCosmeticsMedicinesChange(FoodsCosmeticsMedicines foodsCosmeticsMedicines) {
   beanManager.fireEvent(new PushEvent(foodsCosmeticsMedicines.getBrand()));
}

Finally you just ‘@Observes’ it in some request or application scoped CDI managed bean in the WAR and delegate to a PushContext as below.

@Named
@ApplicationScoped
public class PushBean {

    /**
     *
     */
    private static final String EVENT = "onPush";

    /**
     *
     */
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     *
     */
    @Inject
    @Push(channel = "databaseChannel")
    private PushContext databaseChannel;

    /**
     *
     */
    public PushBean() {
    }

    /**
     * @param pushEvent PushEvent
     */
    public void onPush(@Observes PushEvent pushEvent) {
        logger.info("***** onPush pushEvent getMessage = {}", pushEvent.getMessage());
        databaseChannel.send(EVENT);
    }
}

The PushBean class must be deployed in the WAR file.

In my Facelet (XHTML) I use OmniFaces Socket tag to pick up on the event.

<h:form>
    <o:socket channel="databaseChannel" onmessage="socketListener" >
         <f:ajax event="onPush" render=":tabHomeView:formFoodsCosmeticsMedicines:panelGridFoodsCosmeticsMedicines"/>
    </o:socket>
</h:form>

The onMessage executes some JavaScript, which is contained in:

<head>
    <script src="socketListener.js"></script>
</head>

The socketListener.js is:

function socketListener(message, channel, event) {
    console.log(message);
    console.log(channel);
    console.log(event);
}

When I run my JUnit test that persists a new Document into my MongoDB database the JSF panel grid in my XHTML gets re-rendered displaying the new ‘row’ that has just been inserted. The following appears in the Google Chrome logs:

onPush
11:38:30.550 socketListener.js:3 databaseChannel
11:38:30.550 socketListener.js:4 MessageEvent {isTrusted: true, data: ""onPush"", origin: "ws://localhost:8080", lastEventId: "", source: null, …}
11:38:30.659 jsf.js.xhtml?ln=javax.faces&stage=Development:2001 XHR finished loading: POST "http://localhost:8080/NOTiFYwell/index.xhtml".
AjaxEngine.req.sendRequest @ jsf.js.xhtml?ln=javax.faces&stage=Development:2001
sendRequest @ jsf.js.xhtml?ln=javax.faces&stage=Development:2700
request @ jsf.js.xhtml?ln=javax.faces&stage=Development:2710
ab @ jsf.js.xhtml?ln=javax.faces&stage=Development:3787
OmniFaces.Push.init.onPush @ (index):96
b.readyState.b.onmessage @ omnifaces.js.xhtml?ln=omnifaces&v=3.1:11

And in my JBoss WildFly 12 console I see:

11:38:30,533 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-4) >>>>> onFoodsCosmeticsMedicinesChange getBrand = BONNE MAMAN

11:38:30,533 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-4) >>>>> onFoodsCosmeticsMedicinesChange PushEvent getMessage = BONNE MAMAN

11:38:30,537 INFO  [com.notifywell.push.PushBean] (default task-4) ***** onPush pushEvent getMessage = BONNE MAMAN

The above code was developed using OmniFaces library 3.1 available to download from OmniFaces To make JSF life easier.

See the documentation at OmniFaces Showcase – push Socket.

 

Back to the Future – Has Spring has sprung?

Why it’s time to forgive EJBs for the complexities of 2.x

Spring - an application framework and inversion of control container for the Java platform.

sprung - past participle and past of Spring.

I first encountered Spring in the Summer of 2006 while on contract at a German mobile communications company, which we shall call ‘T’ for brevity. Strange really as I’d been taken on as an EJB developer and for my bea WebLogic Application Server experience. But ‘T’ had software consultants in from the subsidiary of a German car manufacturer and we all know that consultants know best. Never mind the hundreds of thousands of pounds they spent annually on bea WebLogic licences.

Their lead developer was from the Midlands and consequently stayed down Monday to Friday, which meant he carried on programming long after the rest of us had gone home. So when we came in next day some of the code we were working on had been finished. I admired his commitment and appreciated there was little to do in the evenings in a depressed Hertfordshire town whose major industry had flown. But when you’re trying to learn a new framework this was the last thing you needed. Maybe it was this and never having to work out any problems or issues myself that didn’t give me the appreciation of Spring.

Also, I like to consider myself a Java Developer, not an XML configurator and Software Development as a profession, has moved beyond XML. It’s incredibly verbose but that’s just a minor starting gripe. Much more importantly, I don’t want to program in XML.

At this point the usual come back is “you can do Spring with annotations now! No more XML”. But I don’t think for one minute all that legacy Spring, especially sitting around in our financial institutions, has been upgraded to use annotations.

Spring also offered IoC/DI (Inversion of Control & Dependency Injection) out of the box. This was during the J2EE era configured by XML which could go quite overboard.

Back in the dark J2EE ages, when EJB 2.x was (allegedly) extremely difficult to implement Spring may have offered a better alternative which required less Java code (but still tons of XML). J2EE/EJB2 learned lessons from Spring and came up with Java EE 5 which offered the new EJB3 API which was slicker than Spring and required no XML at all.

Move on a decade or so and we now have EE 8. Java EE is now open source and has been taken over from Oracle by The Eclipse Foundation and Java EE renamed Jakarta EE.

  • EJBs no longer require a local or remote interface, Just annotate with @local or @remote
  • JBoss WildFly 12.0.0.Final starts and deploys all your EJBs in under 10 seconds. Try and get anywhere near that with Apache TomCat and Spring.
  • Context Dependency Injection (CDI) is out of the box.
  • JPA 2.2 with support various ORM dialects (Hibernate, EclipseLink, Apache OpenJPA, TopLink).

The EE 8 spec also includes:

  • JavaServer Faces (JSF) 2.3.
  • Java API for WebSockets
  • Java API for JSON Processing (JSON-P)
  • Java API for RESTful Web Services (JAX-RS)
  • Java Servlet
  • Expression Language (EL)
  • Interceptors
  • Java Message Service (JMS)
  • Concurrency Utilities for Jakarta EE
  • Batch Applications for the Java Platform
  • Bean Validation
  • Java Connector Architecture
  • Java Transaction API (JTA)
  • JavaServer Pages (JSP)

All we need is one Financial Institution to say “It’s back to the future” and Spring will have well and truly sprung.