Flamingo Commerce Coding Conventions

Summary:

  • Flamingo Coding Conventions are applied
  • We use Domain Driven Design and Ports and Adapters. Looking in the code should help understanding the e-commerce domain and should be semantic.

Domain Driven Design

Strategical

  • Each module represents an independent subdomain within the overall e-commerce area.
  • Dependencies to modules should be treated just like third-party dependencies. Furthermore, module dependencies should only be introduced as less as possible, when they are useful and remain unidirectional.

Tactical

  • We stick to the Flamingo Module structure and use Ports and Adapters
  • Domain Layer should be technology free and should have potent model in the boundary of the module.
    • Normally should not need to know about Session and Framework specific stuff
  • Application Layer:
    • Should contain thin and useful "Services" or "Handlers" that provide a use case oriented access to the logic of the domain layer.
    • Is allowed to access Session and Framework modules

Configuration

Each module should document its supported configuration using cue.

Runnable (aka FakeAdapters)

Each module should be runnable by default: That means that the modules need to provide so called FakeAdapters for the required secondary ports. The Adapters Binding should be activated by a Feature Flag (Configuration).

Usage of Interfaces

Using interfaces in Go allows us to describe behaviour without specifying an implementation. This proves very helpful when splitting definitions of a service, such as a domain service and the actual implementation, which can be specific to a certain technology/module. Additionally interfaces allow us to generate so-called "mocks", which can be used in unit tests, to assert and mock a certain interaction without actually implementing/using the original functionality.

To prevent tight coupling across modules, each module should introduce their own interfaces which abstract the dependent module's functionnality. Those interfaces should be designed to include only what is needed by its hosting module and must not leak dependent types.

In Flamingo Commerce we agreed in the following:

Usage of interfaces For Secondary Ports

Especially in the domain layer we use interfaces as secondary ports. This is essentially the part where different Adapters can implement the technical details independent from the domain logic.

The modules should contain at least a "FakeAdapter" for this interfaces in the infrastructure layer. This is to demonstrate the use and to be "runnable" by default.

For improved testability

Defining interfaces other then for "secondary ports" makes sense to improve testability. This might be especially useful for application services (living in the application/ folder).

There are two possible solutions to this problem:

  1. one is that the application provider provides an additional interface as well as a default implementation
  2. the other is that the consumer of a service specifies it's own interface.

Approach 1. (define interface in the providing package)

An module should provide an interface for its main application services if useful for testing:

  • The interface has the semantic name
  • The Implementation adds "Impl" to this name and is bound to the interface.

Example:

// */application/service.go
package application

type (
    MyService interface {
      DoSomething()  
    }

    MyServiceImpl struct {

    }   
)

The module will need to Bind the MyServiceImpl for the interface MyService in its Dingo Module.

Other places that need MyService will request the interface MyService - so that you can Unit Test this parts and provide a Mock instead of the Default Implementation.

Approach 2. (define interface in the consuming package)

Use this in cases where the provider does not provide an interface and you want to mock it. E.g. for external libs.

General interface conventions

  • Dont blow up the interface with additional concepts - often its better to add a new interface with the extra "concern" or "concept". Smaller interfaces are better maintainable and it also helps reducing breaking changes.
  • Optional you can provide generated mocks (e.g. using a tool like mockery).