Published on

Flow And Implementation Methods

Authors
  • avatar
    Name
    Danny Mican
    Twitter

Flow and implementation methods are 2 types of functions which help create manintainable software. Flow methods contain a series of program steps and implementation methods contain concrete logic such as making database calls. These method types can be used together to support maintainable software which is easy to understand and verify. Software that ignores these distinct method types risk combinbing busines logic with implementation logic, decreasing maintainability and making software harder to extend and verify.

Flow Methods

Flow methods contain a list of steps, often mapping to business processes. Flow methods are generic message which delegate to more specialized methods. Flow methods are abstract meaning they orchestrate other methods to accomplish work.

The following method illustrates a flow method:

def create_user(request_payload: bytes, users: Users) -> bytes:
	try:
		params = users.deserialize(request_payload)
	except DeserializationError as e:
		raise e

	try:
		users.validate(params)
	except ValidationError as e:
		raise e

	try:
		user = users.get_or_create_user(params)
	except UserCreateError as e:
		raise e

	try:
		return users.serialize(user)
	except SerializationError as e:
		raise e

The only work the flow method perform is invoking methods on its parameters and handling exceptions. A flow method creates a list of operations, in this case the flow of tturning a request into a user is:

  • Deserialize request bytes
  • Validate request
  • Get or create a user based on request
  • Serialize the user

Flow methods do not contain any logic by themselves. Flow methods can be thought of as managers, orchestrating work from their knowledgeable employees. The flow methods have the high level view, and know which implementation method to call in which order to orchestrate value but the employees (implementation methods) are doing the heavy lifting.

Flow Methods are flat and do not contain lots of conditional logic. Flow methods end up following the pattern:

  • Invoke a method
  • Check the response value (return or continue)
  • Invoke a method
  • Check the response value (return or continue)
  • Etc.

Flow methods have a number of properties. Flow methods:

  • Accept all dependencies as arguments (dependency injection)
  • Only invoke methods on their arguments
  • Flat (i.e. limited nesting)

Implementation Methods

Implementation methods contain specific focused business logic. Implemention methods exist on their own but really derive their value when they are orchestrated by a flow method.

Implementation methods are standalone and can exist by themselves. This property enables implementation methods to be tested individually.

import json

class Users:
	def deserialize(users_payload: bytes):
		try:
			data = json.loads(users_payload)
		except ValueError:
			raise DeserializationError

		return data

Implementation methods ability to standalone means that they are decoupled from any business logic through flow methods. Multiple flow methods can reference the users collection. It knows nothing about its callers, all it knows is how to operate on users data. It is a fully standalone entity.

Implementation methods possess a number of properties, implementation methods:

  • Are standalone, and can be instantiated and invoked in multiple contexts
  • Accept their dependencies as argument (dependency injection)

Risks of Not Using

Business logic can easily overlap with implementation logic if careful attention isn't paid to decopuling flow from implementation.

  • Mixed concerns
  • Reverification required whenever code is extended.

The following example illustrates code that mixes concerns by including flow and implementation logic in a single function:

def create_user(request_payload: bytes, users: Users) -> bytes:
	"""
	Deserialize the payload
	This method has multiple reasons to change.
	Changing the deserialization method will trigger a change
	to create_user.
	"""
	try:
		data = json.loads(users_payload)
	except ValueError:
		raise DeserializationError

	try:
		users.validate(params)
	except ValidationError as e:
		raise e

	try:
		user = users.get_or_create_user(params)
	except UserCreateError as e:
		raise e

	try:
		return users.serialize(user)
	except SerializationError as e:
		raise e

In the example above, create_user contains concrete deserialization logic. This means that a change to the deserializtion logic triggers a reverification of the entire create_user method. The method above is said to be tightly coupled to the deserialization logic. Neither the logic nor the create_user method can change indepedently of one another.

Benefits

Structuring programs using flow methods and imeplementation methods increase maintainability, verifiabilty and extensibility. Flow methods provide a recipe of the steps necessary to orchestrate some business logic, while implementation methods encapsulate concrete logic.

Maintainability

Flow and Implementation methods increase maintainability through decoupling. If a bug occurs the problem space can be partitioned along business process or implementation issue. For example, if a bug occurs with inserting a user into a database, investigation can start with the corresponding implementation method. The same goes for business process issues. When a bug or error occurs with business processes investigation can begin with the flow method. Are the logical steps correct?

Verifiability

Depedency injection makes Flow and Implementation methods trivial to verify at a unit test level. Both methods lend well to extremely focused tests. It's easy to verify all branches in flow methods since they are "flat" (they don't contain nested logic). Implementation methods ar also easy to verify since they are focused standalone functionality.

Extensibility

Flow and Implementation methods are optimized for extensibilty. Logic can be refactored in flow methods without impacting any of the concrete implementation methods. Their decoupled nature allows teams to work on different componnts in isolation. A new implementation can safely be developed alongside the current implementation and then introduced to the flow method only after its been fully verified.

Conclusion

Flow and Implementation methods are easy to understand when issues occur, easy to verify due to their decoupled nature, and easy to extend.


If you found this pattern helpful and you're interested in learning more about how to make Maintainable, Verifiable and Extensible software check out my book Professional Software Principles Using Python.