prep

Layered Architecture

Learning Objectives

Self Study

As you read through the resources below try to answer the following questions:

  • In a sentence or two, briefly summarise what we mean by ‘software architecture’?
  • Why is a layered architecture useful? Think in particular about complex software systems that are live in production for a long time (and hence technology evolves!), and where maintainability, testability and reliability are important, where changes are constantly being made and where lots of developers in different teams are making changes at the same time
  • How do interfaces help to decouple the layers in an application and why is this decoupling useful?
  • Can you think of a project you’ve worked on where you used a layered architecture? If so, did it help? If you’ve never used a layered architecture before, can you think of a project that could be improved if refactored to do so?

Resources

  • Introduction to software architecture - read sections Introduction to software architecture, Importance of software architecture and Software architecture design (the rest discusses architecture design patterns so feel free to read that too!)
  • Good overview of layered architecture - focus on why layered architecture is useful, familiarise yourself with the UI, business logic and database layers, and look at the example application

Exercises

✍️Exercise 1.1

  • For a previous project you have worked on, describe the tiers of architecture used. Draw a diagram of the architecture tiers used and the key components and software they use (e.g. React front end, Java Service layer, Hibernate DAO and Postgres DB).

  • Consider a social media application where users can write posts on a forum. Consider the process of a user adding a new post - which tier of the application would the code for the following actions live?

  • Handling the user interaction to navigate to the ’new post’ screen

  • Posting the callback to refresh the page when the user saves the post

  • Validating that the post doesn’t contain any banned words or phrases after the user hits save

  • Scanning the contents of the post to add suitable tags to make it appear in relavent search results

  • Loading a draft post that was previously saved

  • Storing the the post long term

Dependencies and Dependency Injection

Learning Objectives

Self Study

As you read through the resources below try to answer the following questions:

  • What do we mean by a ‘dependency’ in a Java class?
  • What is Inversion of Control (IoC) and how does it relate to Dependency Injection (DI)?
  • Can you give an example of IoC that isn’t DI?
  • What are some potential issues with Java classes that have dependencies but don’t use DI. Think about testability, maintainability and tight-coupling for large code bases that evolve over time.
  • How does DI help solve the above issues?
  • What is constructor injection and how is it different to method injection?

Resources

Exercises

✍️Exercise 2.1

Refactor the code below to use constructor based dependency injection:

 public static void main(String[] args) {
    Car car = new Car();
    car.start();
  }
 
  static class Car {
    Engine engine = new Engine();
 
    void start() {
      engine.rev();
      System.out.println("Car Started");
    }
 
  }
 
  static class Engine {
    Gears gears = new Gears();
    Cambelt cambelt = new Cambelt();
 
    void rev() {
      gears.initialise();
      cambelt.initialise();
    }
  }
 
 
  static class Gears {
    Set<Cog> cogs = Set.of(new Cog(), new Cog(), new Cog());
 
    void initialise() {
      cogs.forEach(cog -> cog.spin());
 
      // Initialisation logic
    }
  }
 
  static class Cog {
    void spin() {
      // Spin logic
    }
  }
 
 
  static class Cambelt {
    void initialise() {
      // Initialisation logic
    }
  }

Reflections

Think about the following questions, make notes and be prepared to talk through your thoughts in the workshop.

  • Suppose we wanted to refactor our car such that it supported multiple different types of engine. How might we do it?
  • How would unit testing the Car class be easier after it was refactored to use DI? Remember the unit tests for Car should not need to also test Engine, Gears, Cogs etc
  • Why in this example (and in general) do we prefer constructor injection to setter injection?

The Service Layer

Learning Objectives

Self Study

As you read through the resources below try to answer the following questions:

  • Where should simple business logic live? e.g. for a Car class with an Engine dependency, in which class should the code to validate that it’s engine must be less than 10 years old live?
  • When should standalone service classes be used?
  • Should service classes be interfaces or concrete classes? Always? Why? Think about reusability, maintainability, testability.
  • What do dependency injection frameworks do and how do they help with dependency injection of services? Note we aren’t going to be using any DI frameworks yet, but it’s good to understand the motivation for them.

Resources

✍️Exercise 3.1

Build a Payment processor that needs to work with different providers:

  • Create a PaymentProcessor interface with methods:
    • boolean processPayment(double amount)
    • boolean refund(String transactionId)
    • String getProviderName()
  • Create THREE implementations (in reality each implementation would contain the code to call to the relevant API, but we’ll skip that part here!):
    • StripePaymentProcessor:
      • processPayment: prints “Processing £X via Stripe…”
      • Returns true (simulate success)
    • PayPalPaymentProcessor:
      • processPayment: prints “Processing £X via PayPal…”
      • Returns true (simulate success)
    • DummyPaymentProcessor:
      • Doesn’t actually process anything
      • Just tracks method calls for testing
      • Stores: List of amounts processed, List of refund IDs
  • Create CheckoutService:
    • Has PaymentProcessor dependency which should be injected in the constructor
    • Method: processCheckout(List<Double> amounts)
      • Calculates total
      • Uses PaymentProcessor to charge
      • Returns success/failure
  • Create a main method demonstrating:
    • Production with Stripe
      • PaymentProcessor stripe = new StripePaymentProcessor();
      • CheckoutService checkoutService = new CheckoutService(stripe);
      • checkoutService.processCheckout(cart);
    • Production with PayPal
      • PaymentProcessor paypal = new PayPalPaymentProcessor();
      • CheckoutService checkoutService2 = new CheckoutService(paypal);
      • checkoutService2.processCheckout(cart);
  • Testing
    • DummyPaymentProcessor dummyProcessor = new DummyPaymentProcessor();
    • CheckoutService dummyService = new CheckoutService(testProcessor);
    • dummyService.processCheckout(cart);
    • Verify: dummyProcessor.getProcessedAmounts() contains expected values

Reflections

Think about the following questions, make notes and be prepared to talk through your thoughts in the workshop.

  • Did CheckoutService change when switching processors?
  • Could you add a new processor without changing CheckoutService?
  • Suppose we updated the implementations above to call the real APIs
  • What would we need to change if Stripe’s API changes?
  • How does having the specific dummy test implementation simplify testing?