2. Who am I?
- Senior PHP Engineer (Contractor) via Adeva
- Technical Content Creator
- Mentor
- Have worked on small startups, scale-ups and huge enterprise distributed systems
- Full-stack developer turned backend software engineer
- Have worked with PHP, Laravel, Symfony, JavaScript, MySQL, Redis, ElasticSearch etc.
- Love discussing technical ideas and experimenting with them on my personal projects
- More info at DavorMinchorov.com
- #CRUDScience
3. It’s all about the context you are in
Developers build different types of applications and software in different contexts
- Brand Websites
- Packages / libraries
- Small bootstrapped Software As A Service Startup
- VC-funded startup
- Scale-up software
- Enterprise Software
All of these have different types of problems at the scale they are at
They all get to the unavoidable business complexity and unmaintainable code after a while
The context that I will cover will be a startup that is at least 3 years in production and beyond
4. Over-engineering vs Under-engineering
Over-engineered code is usually a phrase that is thrown around for code that is either:
- Simple, full of abstractions and can be changed easily or new code can be added when the
product grows over time
- badly written and completely unnecessary
On the other hand, under-engineered code is usually either:
- Written quickly and completely unmaintainable for the long run
- It requires many changes all over the place in order to add new code
Simple code in one context is complex code in another one.
7. Over-engineering vs Under-engineering
So what determines which code is over-engineered or under-engineered?
- The context which it’s written in
- The level of maintainability and the ability to change it easily when the business rules and
processes become more complex over time.
The first code example written in an a scale-up or an enterprise software is very simple and easy
to maintain.
The second code example written in a brand website, a personal website, or a throw-away
project is very simple and easy to maintain.
If the two code examples switch their contexts, they will become over-engineered and under-
engineered respectively.
8. Accidental vs essential complexity
As business rules and processes grow and change over time, the product becomes more
complex and this is called essential complexity. You can’t avoid it.
The first code example will be easier to maintain, change and extend simply by adding
- event listeners to react to the events that are being dispatched
- add additional business rules within the Member entity.
- configure the inversion of control container to use different implementations
You wouldn’t need to touch the class in the example.
The business has complex enough use cases which makes sense for the code to be written this
way.
This may be a very simple code for this specific context.
9. Accidental vs essential complexity
The second example usually makes you think that you can add additional code in the controller
making it work based on the feature requirements as they come in.
Let’s say you add code related to
- member role assignment
- if statement to check if the member is coming from an API or a web request
- Send a welcome and verification email
- sign up member to a newsletter,
- Create audit log
- And so on
This is called creating accidental complexity by not creating additional files and folders and
splitting the code.
12. Framework coupling vs Framework decoupling
All frameworks are opinionated general solutions which try to save us time and lower the
amount of code that we need to write and maintain
They help us focus on the main problem which is automating business processes
Developers learn to use a framework and they use it as much as possible to solve a specific
problem with a general solution
Frameworks dictate how you write and structure your code but they don’t know about your
business
This is called framework coupling - your business code depends on your framework of choice
and makes it harder to change over time
13. Framework coupling vs Framework decoupling
There are a few reasons why this may be bad:
- Framework and language upgrades slow down and the code becomes harder to upgrade
when the direct implementation is being used from the framework
- The risk of breaking the product because the required changes which affect the whole
product all over the place
- Moving out code into different projects will be harder when new teams are formed when
it’s time to scale
- Changing implementations will require many changes all over the place
14. Framework coupling vs Framework decoupling
Framework decoupling solve these problems by coding to an interface, not to an
implementation
The benefits are:
- You can start building custom components specific to the business needs
- Freedom to structure the code however you want based on the business requirements
- The framework is a tool
- It makes it easier to design and test the code
- Far fewer changes will be required during upgrades
It may take a while to get used to thinking and working this way, which may be slower due to
team inexperience
15. Thinking data (CRUD) vs thinking business
processes (behaviour)
Developers are taught to build projects with the CRUD mindset at the start of their careers by
many tutorials, books, academies, universities and other resources
The business processes are neglected and the first thing people build is the database schema
Whenever a business process is executed, the process does not just save data in the database
- It may validate the input values based on specific business rules and requirements
- It may talk to a 3rd party service
- It may store the data into multiple databases
- It may talk to multiple services or applications
Business people are forced to learn technical terms to communicate with developers
16. Thinking data (CRUD) vs thinking business
processes (behaviour)
The business use cases are usually neglected and the product is being built in a Excel like
software
it’s very technical, will eventually become complex to manage and work with such code if it’s
written with the database-first mindset which brings accidental complexity
Business people don’t care about how the product is being built, they just want fast changes and
additional features
Developers focus on the database and what columns it will have, what data types they will be
and then they start writing the code based on the data in the database.
Designing the database with best practices in mind is important but not to the business
17. Thinking data (CRUD) vs thinking business
processes (behaviour)
The people who use and design the business processes think about the
use cases like:
- Choose the restaurant and the food to order
- Apply coupon
- Pay for the order
- Approve Order
- Take an order to deliver
- Pick up order from restaurant
- Take order to the customer delivery destination
- Complete the order delivery
18. Thinking data (CRUD) vs thinking business
processes (behaviour)
Understanding the business domain is key in order to build software that the customers need
It’s easier to:
- Name files, modules, classes, variables, methods and properties
- Understand what the business people are saying and what they need
- Communicate with a ubiquitous language
- Design the business related code without any infrastructure concerns (database for example)
Behaviour driven development and domain driven design may be useful in such cases
It may be harder to switch the mindset to think and work this way, it may take years to get used to it
20. Planning
Developers plan their code and database but they rarely plan what they need to do
There may be many ways of planning business processes but I would mention:
- Event Storming
- Event Modelling
- Event Storytelling
They are somewhat different in how they work but they have similarities that focus on the
events that happen in the product from a behaviour point of view.
The idea of these planning processes is for customers, business people and technical people to
get into a room, and discover what and how the business processes will work in order for the
technical people to build them.
22. Planning
Having a plan of what the business use cases and rules are makes it easier to map that plan into
code
It helps with figuring out what the modules of the application will be and how the code will be
structured
Some of those modules can be separate applications (whenever it makes sense)
Exploring the domain also helps people to get to know the business better, it brings out ideas
out of their heads
A lot of questions will be answered during these planning sessions
The goal is not to expect or chase the complexity but rather let it evolve and manage it
27. Code Refactoring
The primary responsibility of a controller is to take a request data, to pass that data off to some
other object to process it, and return a response
SignUpMemberController
- The route is defined using attributes
- A SignUpMember command is being dispatched via the command bus dispatcher
- A No Content response is being returned
28. Code refactoring
A command is an input data transfer object that the primary responsibility of the class is to
carry data
It can have getters and setters or public readonly properties which will be initialized in the
constructor on initialization.
Commands do not have any behaviour.
The command bus pattern allows you to separate your code between the layers by taking
request data in a form of a command class and passing it through a bus, handled by a command
handler class.
The command handler class handles a single use case, can be decorated and it can be queued in
the background if the processing takes a while to complete.
31. Code Refactoring
A value object is a class which holds a primitive value, validate their format and makes sure that
it’s consistent so that it can passed over into the other layers.
A value object can hold multiple values as well (example: Address, Money, Coordinates)
Value objects in the code example:
- ID
- First Name
- Last Name
- Email Address
- Password
37. Code Refactoring
Rich domain models are classes which represent business processes from the real world and
they mainly contain business rules and behaviours
Anemic domain models are usually classes which represent the business processes from the real
world but they only contain getters and setters and are not recommended.
Rich domain models can sometimes be called write models when using CQRS
Domain models live in the heart of the domain layer
Rich domain models raise events whenever something happens in the application
All events are being dispatched at once in the order they were raised at the end of the use case.
39. Code Refactoring
Repositories are classes or components that encapsulate the logic required to access data
sources.
data source examples:
- MySQL
- ElasticSearch
- MongoDB
- DynamoDB
- Internal microservice REST API
- A Message Broker
- File
43. Code Refactoring
Read models are output data transfer objects that only contain data, no behaviour
They are usually used for picking up data from different data sources
They are alternatives to arrays
They are typed and you can easily see what kind of data to expect from the output
45. Code Refactoring
Events are input data transfer objects which contain only data and no behaviour
They are usually named in a past tense because they represent something that has happened in
the application
The event dispatcher dispatches events and multiple event listeners can listen to a single event
and react to that event
Event listeners, similar to command handlers, can be decorated and queued for background
processing
Events can be stored into an event store when event sourcing is used
47. Conclusion
Today you learned something new about:
- It’s all about the context
- Over-engineering vs Under-engineering
- Accidental vs Essential complexity
- Framework coupling vs Framework decoupling
- Thinking data (CRUD) vs thinking business processes (behaviour)
- The misconceptions of object-oriented programming and design, design patterns and
principles
- Planning
- Code refactoring