Converter Pattern using Spring Dependency Injection

1. Introduction

In this article, I will talk about the converter pattern using Spring’s Dependency Injection. We constantly find ourselves writing request or response mapping code. This code is usually translating an object of Type T to Type R. I will show you how to break down big mappings into smaller ones using Dependency Injection.

2. Content

I will use a request conversion for PayPal invoicing APIs, as an example.

Reference: https://developer.paypal.com/docs/api/invoicing/v2/#invoices_create 
API: https://api-m.sandbox.paypal.com/v2/invoicing/invoices
Body:

{
  "detail": {
    "invoice_number": "#123",
    "reference": "deal-ref",
    "invoice_date": "2018-11-12",
    "currency_code": "USD",
    "note": "Thank you for your business.",
    "term": "No refunds after 30 days.",
    "memo": "This is a long contract",
    "payment_term": {
      "term_type": "NET_10",
      "due_date": "2018-11-22"
    }
  },
  "invoicer": {
    "name": {
      "given_name": "David",
      "surname": "Larusso"
    },
    "address": {
      "address_line_1": "1234 First Street",
      "address_line_2": "337673 Hillside Court",
      "admin_area_2": "Anytown",
      "admin_area_1": "CA",
      "postal_code": "98765",
      "country_code": "US"
    },
    "email_address": "merchant@example.com",
    "phones": [
      {
        "country_code": "001",
        "national_number": "4085551234",
        "phone_type": "MOBILE"
      }
    ],
    "website": "www.test.com",
    "tax_id": "ABcNkWSfb5ICTt73nD3QON1fnnpgNKBy- Jb5SeuGj185MNNw6g",
    "logo_url": "https://example.com/logo.PNG",
    "additional_notes": "2-4"
  },
  "primary_recipients": [
    {
      "billing_info": {
        "name": {
          "given_name": "Stephanie",
          "surname": "Meyers"
        },
        "address": {
          "address_line_1": "1234 Main Street",
          "admin_area_2": "Anytown",
          "admin_area_1": "CA",
          "postal_code": "98765",
          "country_code": "US"
        },
        "email_address": "bill-me@example.com",
        "phones": [
          {
            "country_code": "001",
            "national_number": "4884551234",
            "phone_type": "HOME"
          }
        ],
        "additional_info_value": "add-info"
      },
      "shipping_info": {
        "name": {
          "given_name": "Stephanie",
          "surname": "Meyers"
        },
        "address": {
          "address_line_1": "1234 Main Street",
          "admin_area_2": "Anytown",
          "admin_area_1": "CA",
          "postal_code": "98765",
          "country_code": "US"
        }
      }
    }
  ],
  "items": [
    {
      "name": "Yoga Mat",
      "description": "Elastic mat to practice yoga.",
      "quantity": "1",
      "unit_amount": {
        "currency_code": "USD",
        "value": "50.00"
      },
      "tax": {
        "name": "Sales Tax",
        "percent": "7.25"
      },
      "discount": {
        "percent": "5"
      },
      "unit_of_measure": "QUANTITY"
    }
  ],
  "configuration": {
    "partial_payment": {
      "allow_partial_payment": true,
      "minimum_amount_due": {
        "currency_code": "USD",
        "value": "20.00"
      }
    },
    "allow_tip": true,
    "tax_calculated_after_discount": true,
    "tax_inclusive": false,
    "template_id": "TEMP-19V05281TU309413B"
  },
  "amount": {
    "breakdown": {
      "custom": {
        "label": "Packing Charges",
        "amount": {
          "currency_code": "USD",
          "value": "10.00"
        }
      },
      "shipping": {
        "amount": {
          "currency_code": "USD",
          "value": "10.00"
        },
        "tax": {
          "name": "Sales Tax",
          "percent": "7.25"
        }
      },
      "discount": {
        "invoice_discount": {
          "percent": "5"
        }
      }
    }
  }
}

3. Bad way to do Request Mapping

You can put the entire mapping in one class. 

You can start off by creating a class.

public final class InvoiceRequestConverter {

    public Invoice to() {
       Invoice invoice = new Invoice();
       return invoice;
    }
}

Now you can put values in the Invoice object using its setter methods.

public final class InvoiceRequestConverter {

    public Invoice to() {
       Invoice invoice = new Invoice();
       invoice.setInvoicer(getInvoicer());
       invoice.setAmount(getAmount());
       invoice.setConfiguration(getConfiguration());
       invoice.setDetail(getDetail());
       invoice.setItems(getItems());
       invoice.setPrimaryRecipients(getPrimaryRecipients());
       return invoice;
    }

    private Invoicer getInvoicer() {
       Invoicer invoicer = new Invoicer();
       invoicer.setAddress(getAddress());
       invoicer.setAdditionalNotes("additional notes");
       invoicer.setEmailAddress("email");
       invoicer.setLogoUrl("");
       invoicer.setName(getName());
       invoicer.setTaxId("");
       invoicer.setWebsite("https://www.justamonad.com");
       return invoicer;
    }

    private Address getAddress() {
       Address address = new Address();
       address.setAddressLine1("123 Fake ST");
       address.setAddressLine2("APT 123");
       address.setAdminArea1("San Jose");
       address.setCountryCode("US");
       address.setPostalCode("95112");
       return address;
    }

    private Name getName() {
       Name name = new Name();
       name.setGivenName("John");
       name.setSurname("Doe");
       return name;
    }

    private Amount getAmount() {
       Amount amount = new Amount();
       amount.setCurrencyCode("USD");
       amount.setValue("10.00");
       return amount;
    }

    // Other methods go here.

}

This class file gets bigger if the request mapping is large. Can we do better?

4. Using Dependency Injection

The idea here is to break all the mappings into small classes. These classes will have their own set of mappings. 

Now the code looks pristine. With this code, you can test the individual class’s functionality. You can also integrate the common components by just injecting the dependencies.

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

@AllArgsConstructor
@Component
public final class InvoiceRequestConverter {

   private final InvoicerConverter invoicerConverter;
   private final AmountConverter amountConverter;
   private final ConfigurationConverter configurationConverter;
   private final DetailConverter detailConverter;
   private final ItemsConverter itemsConverter;
   private final PrimaryRecipientsConverter primaryRecipientsConverter;

   public Invoice to() {
      Invoice invoice = new Invoice();
      invoice.setInvoicer(invoicerConverter.to());
      invoice.setAmount(amountConverter.to());
      invoice.setConfiguration(configurationConverter.to());
      invoice.setDetail(detailConverter.to());
      invoice.setItems(itemsConverter.to());
      invoice.setPrimaryRecipients(primaryRecipientsConverter.to());
      return invoice;
   }
}

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

@AllArgsConstructor
@Component
public final class InvoicerConverter {

   private final AddressConverter addressConverter;
   private final NameConverter nameConverter;

   public Invoicer to() {
       Invoicer invoicer = new Invoicer();
       invoicer.setAddress(addressConverter.to());
       invoicer.setAdditionalNotes("additional notes");
       invoicer.setEmailAddress("email");
       invoicer.setLogoUrl("");
       invoicer.setName(nameConverter.to());
       invoicer.setTaxId("");
       invoicer.setWebsite("https://www.justamonad.com");
       return invoicer;
   }

}

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

@Component
public final class AddressConverter {

   public Address to() {
       Address address = new Address();
       address.setAddressLine1("123 Fake ST");
       address.setAddressLine2("APT 123");
       address.setAdminArea1("San Jose");
       address.setCountryCode("US");
       address.setPostalCode("95112");
       return address;
   }

}

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

@Component
public final class NameConverter {

   public Name to() {
       Name name = new Name();
       name.setGivenName("John");
       name.setSurname("Doe");
       return name;
   }

}

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

@AllArgsConstructor
@Component
public final class ConfigurationConverter {

   private final PartialPaymentConverter partialPaymentConverter;

   public Configuration to() {
       Configuration configuration = new Configuration();
       configuration.setAllowTip(Boolean.TRUE);
       configuration.setPartialPayment(partialPaymentConverter.to());
       configuration.setTaxInclusive(Boolean.TRUE);
       configuration.setTemplateId("template id");
       configuration.setTaxCalculatedAfterDiscount(Boolean.TRUE);
       return configuration;
   }

}

5. Conclusion

In this article, we saw that doing request/response mappings we should use a Converter Pattern. This can break down code from larger classes into smaller ones. Now, with this code, you can easily integrate and test.

Leave a Reply

Your email address will not be published. Required fields are marked *