Entity view (Content)

Component binding with inbound endpoints

By aabed
May. 23, 2017

In a recent project I faced a situation to port some complex business logic into mule app. I know that this is not an ideal situation and we all should think of dumb pipes and move the business components into separate micro-services that may not even be implemented in mule. But it is what it is and I had to make that decision then and move this piece of logic into mule at least temporarily.

The piece of code looks something like the following:

try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?useSSL=false", "myuser", "mypassword");
     Statement stmt = conn.createStatement();) {
    ResultSet rs = stmt.executeQuery("select id, firstName, lastName, salary from employee");
    while(rs.next()) {
        int id = rs.getInt("id");
        String firstName = rs.getString("firstName");
        String firstName = rs.getString("lastName");
        double salary = rs.getDouble("salary");
        //
        // do some logic here
        //
        if (someCondition) {
            statement.executeUpdate("insert into table1 values (...)");
        }
        //
        // another piece of login here
        //
        statement.executeUpdate("update table2 set ...");
        //
        // ... etc
        //
    }
} catch(SQLException ex) {
    ex.printStackTrace();
}

To move this logic into Mule flow, you just need to create a custom processor or Java component and move this code into it. This is the easy part. But there are multiple DB i/o operations in this code and it would be great if we can use Mulesoft built-in DB connection and operations components instead of this boiler-plate JDBC code. We can do that using Mulesoft components bindings. More info is in the link below:

https://docs.mulesoft.com/mule-user-guide/v/3.8/component-bindings

So the code above will end up similar to the following in Mule:

<component class="com.appnovation.component.MyComponent">
    <binding interface="com.appnovation.component.MyBinding" method="query">
        <flow-ref name="subflow-query" />
    </binding>
    <binding interface="com.appnovation.component.MyBinding" method="insert">
        <flow-ref name="subflow-insert" />
    </binding>
    <binding interface="com.appnovation.component.MyBinding" method="update">
        <flow-ref name="subflow-update" />
    </binding>
</component>
<sub-flow name="subflow-query">
    <db:select config-ref="dbConfig">
        <db:parameterized-query><![CDATA[select id, firstName, lastName, salary from employee]]></db:parameterized-query>
    </db:select>
    <!-- a dataweave components to convert the query result map into an Employee java object list -->
</sub-flow>
<sub-flow name="subflow-insert">
    <!-- parameters are passed as an array in the payload; param1 = payload[0] and param2 = payload[1] --> 
    <db:insert config-ref="dbConfig">
        <db:parameterized-query><![CDATA[insert into table1 values (...)]]></db:parameterized-query>
    </db:insert>
</sub-flow>
<sub-flow name="subflow-update">
    <!-- single parameter passed in the payload; emp = payload --> 
    <db:update config-ref="dbConfig">
        <db:parameterized-query><![CDATA[insert into table1 values (...)]]></db:parameterized-query>
    </db:update>
</sub-flow>
public class MyComponent implements Callable {
    private MyBinding binding;
    public void setUserBinding(MyBinding binding) {
        this.binding = binding;
    }
    @Override
    public Object onCall(MuleEventContext eventContext) throws Exception {
        MuleMessage message = eventContext.getMessage();
        List<Employee> list = binding.query();
        for (Employee emp : list) {
            //
            // do some logic here
            //
            if (someCondition) {
                binding.insert(param1, param2);
            }
            //
            // another piece of login here
            //
            binding.update(emp);
            //
            // ... etc
            //
            return message; 
        }
    }
}
public interface MyBinding {
    List<Employee> query();
    void insert(Employee emp);
    void update(Employee emp);
}

The only problem in the code above is that Mule component binding is design for outbound endpoints only. If you run the code above you will get the error:

Invalid content was found starting with element 'flow-ref'. One of '{"http://www.mulesoft.org/schema/mule/core":abstract-outbound-endpoint}' is expected

To solve that, We need to change the flow calls from flow-ref into vm i/o (and of course change the sub-flows into flows to allow adding an inbound vm).

The Mule flow will look like the following then:

<component class="com.appnovation.component.MyComponent">
    <binding interface="com.appnovation.component.MyBinding" method="query">
        <vm:outbound-endpoint exchange-pattern="request-response" path="/query" />
    </binding>
    <binding interface="com.appnovation.component.MyBinding" method="insert">
        <vm:outbound-endpoint exchange-pattern="request-response" path="/insert" />
    </binding>
    <binding interface="com.appnovation.component.MyBinding" method="update">
        <vm:outbound-endpoint exchange-pattern="request-response" path="/update" />
    </binding>
</component>
<flow name="flow-query">
    <vm:inbound-endpoint exchange-pattern="request-response" path="/query" />
    <db:select config-ref="dbConfig">
        <db:parameterized-query><![CDATA[select id, firstName, lastName, salary from employee]]></db:parameterized-query>
    </db:select>
    <!-- a dataweave components to convert the query result map into an Employee java object list -->
</flow>
<flow name="flow-insert">
    <vm:inbound-endpoint exchange-pattern="request-response" path="/insert" />
    <!-- parameters are passed as an array in the payload; param1 = payload[0] and param2 = payload[1] --> 
    <db:insert config-ref="dbConfig">
        <db:parameterized-query><![CDATA[insert into table1 values (...)]]></db:parameterized-query>
    </db:insert>
</flow>
<flow name="flow-update">
    <vm:inbound-endpoint exchange-pattern="request-response" path="/update" />
    <!-- single parameter passed in the payload; emp = payload --> 
    <db:update config-ref="dbConfig">
        <db:parameterized-query><![CDATA[insert into table1 values (...)]]></db:parameterized-query>
    </db:update>
</flow>

You will notice that AnypointStudio will complain about the VM components inside the bindings (Error: VM is not allowed to be child of element binding). This is a bug in AnypointStudio. VM are allowed inside the binding element and when you run the code Mule runtime will execute them normally.

 

Post Tags: