28
Keeping business logic out of your UIs Petter Holmström, Vaadin Architect [email protected]

Keeping business logic out of your UIs

Embed Size (px)

Citation preview

Page 1: Keeping business logic out of your UIs

Keeping business logic out of your UIs

Petter Holmström, Vaadin [email protected]

Page 2: Keeping business logic out of your UIs

What is “business logic”?

● Every information system exists to solve a real-world problem (I hope...)● A real-world problem consists of static parts (things, concepts) and dynamic

parts (events, processes)● A system uses a model of the real world to solve its problems

○ The domain model

● The static parts of the domain model are often the easy ones● The dynamic parts are easily forgotten even though they are just as important● The business logic is the implementation of the dynamic parts of the domain

model

Page 3: Keeping business logic out of your UIs

Disclaimer:Business logic creeping into the UI is not really an architectural problem but a problem with developer discipline

Page 4: Keeping business logic out of your UIs

Let’s look at a “classic” architecture

Persistence layer (JPA)

Service layer (EJB)

UI layer (Vaadin)

Entities

SQL database

Page 5: Keeping business logic out of your UIs

Problem 1

UI Operation

Service 1

Service 2

Service 3

Actual transactionboundaries

Logical transaction boundary

Page 6: Keeping business logic out of your UIs

Problem 2

UI CRUD Service+

Business logic in the user’s head and in the UI

Service layer is only a frontend to the database

Page 7: Keeping business logic out of your UIs

Problem 3

UI 1

UI 2

UI 3

God Service

Page 8: Keeping business logic out of your UIs

Problem 4

Service A Service B

New method

?

Page 9: Keeping business logic out of your UIs

What can we do?

Page 10: Keeping business logic out of your UIs

Think about the transaction boundary

● Pay attention while coding and while reviewing others’ code● The transaction boundary should IMO go between the UI and your backend● Rules of thumb:

○ One UI operation, one backend call○ One backend call, one transaction○ One transaction, one thread

● Imagine your backend is running on a remote machine and you want to minimize the traffic

Page 11: Keeping business logic out of your UIs

Take care of your services

● Strive for highly cohesive services● Avoid too general names like CustomerService● Instead, opt for more specific names like CustomerOnboarding● If you are adding a new service method and you find you could add it to more

than one service, you should probably create a completely new one or refactor the existing ones

Page 12: Keeping business logic out of your UIs

Separate reading and writing

● Place your queries in separate components● Create UI specific query components instead of adding more and more query

methods to existing ones● Allows for some neat optimizations and scaling

Page 13: Keeping business logic out of your UIs

Avoid CRUD

● CRUD UIs and services basically move the business logic into the head of the user

● Base your UIs and services on actual business processes and operations● Exception: management UIs for reference and master data

Page 14: Keeping business logic out of your UIs

Introducing a Command based architecture

Page 15: Keeping business logic out of your UIs

● Think in terms of Commands and Queries● A Command tells the system to perform a specific business operation

○ A command may return some result○ A command either completes successfully or fails with an error

● A Query asks the system for specific data○ A query always returns something○ A query only fails because of external runtime errors such as network problems

● Commands and queries can be synchronous or asynchronous● You can model each command and each query as a separate class

Forget about services!

Page 16: Keeping business logic out of your UIs

The query/command class

● The name is always a verb in its imperative form● The parameters are class attributes● Use a generic parameter for the result, if any● Try to make your class immutable

○ Not always possible, e.g. if you are binding a form directly to your command

● Validate the parameters inside your class○ If you do this in the constructor, you cannot create a command or query with invalid parameters

Page 17: Keeping business logic out of your UIs

Examples

<<command>>CreatePatient<Patient>

firstName: string

lastName: string

gender: Gender

birthDate: LocalDate

<<query>>FindOrders<List<Order>>

from: LocalDate

to: LocalDate

customer: Optional<Customer>

Page 18: Keeping business logic out of your UIs

Examples (code)

public class CreatePatient implements Command<Patient> {

final String firstName;

final String lastName;

final Gender gender;

final LocalDate birthDate;

public CreatePatient(String firstName, String lastname,

Gender gender, LocalDate birthDate) {

this.firstName = Objects.requireNonNull(firstName);

… }

}

Page 19: Keeping business logic out of your UIs

Examples (code)

public class FindOrders implements Query<List<Order>> {

final LocalDate from;

final LocalDate to;

final Optional<Customer> customer;

public FindOrders(LocalDate from, LocalDate to) {

… }

public FindOrders(LocalDate from, LocalDate to,

Customer customer) {

… }

}

Page 20: Keeping business logic out of your UIs

What about the implementation?

● Technically, queries and commands are implemented in the same way● Each command/query has a handler● The handler contains a single method that accepts the command/query as a

single parameter and returns the result of the command/query● Handlers are transactional● Similar queries/commands can share a common base class● Handlers can invoke other handlers

Page 21: Keeping business logic out of your UIs

Finding the correct handler

● A client will never invoke another handler directly● Instead, commands/queries are passed to a broker or gateway● The broker/gateway will look up the correct handler, invoke it and return the

result

Page 22: Keeping business logic out of your UIs

Advantages

● An easy way of modeling the dynamics of the problem domain● Forces you to think in terms of the domain while designing your UI

○ Since your UI should only be constructing and invoking commands/queries, the risk of business

logic ending up there should be smaller (I hope)

● Your business logic consists of small, highly cohesive classes● No more god classes● Easy to distribute, e.g. by using a service bus

○ Command and queries could be serialized into JSON○ Command and query handlers can be running anywhere

● Easy to run asynchronously● You can use separate data sources for reading and writing

○ CQRS

Page 23: Keeping business logic out of your UIs

Master SlaveSlave

Query Handler Query HandlerCommand Handler

Broker

UI

Page 24: Keeping business logic out of your UIs

Disadvantages

● More boilerplate code: for each operation, you have to write a command/query class and a handler class

● The client does not invoke the handler directly so if the handler is missing, it will not be detected until during runtime

● You have to write the command/query framework yourself● You can still write too coarse or too fine grained commands

Page 25: Keeping business logic out of your UIs

Time for a code example!

Page 26: Keeping business logic out of your UIs

What about long-running transactions?

● Many commands participate in the same conversation● If your container supports it, you can make your handlers conversation scoped

○ This makes it possible to store state in the handlers

● If you want your handlers to remain stateless, create a conversation object that is returned by and passed to every command handler

● The conversation object contains the state necessary to move the conversation forward

Page 27: Keeping business logic out of your UIs

Summary

● Discipline vs patterns● Transaction boundary● High cohesion● Separate read and write● Avoid CRUD● Consider using commands and queries in business driven user interfaces

Page 28: Keeping business logic out of your UIs

That’s all folks!