# Automating Software Development
Developing software usually requires many steps until we have a final product. From the conception to drawing basic mockups and then actually breaking it down to individual features and implementing them. Although many methodologies on how to build software have been created and used over the years the main goal is to define a plan to materialize an idea.
Usually software is build to solve a problem or we could say, to automate a process. Often this is referred as the Domain. Leaving the research and development of what we want to built aside for a moment, usually in the end we have to implement a set of features in order to "solve" the problem.
A feature typically is a natural language description of one functionality of our Domain. Those natural language descriptions are organized with various methods in order to prioritize and estimate the cost of implementation.
Implementing a feature typically consist of what we actually want to do, for example having a list of customers, and many different layers and mechanisms in order to make that possible. Taking the example of presenting a list of customers we will most likely do:
- define the model of our data for this and where it is going to be saved (typically a database).
- define endpoints
- define authentication rules
- define authorization rules
- define the structure of our request
- define the structure of our response
- define the presentation. what the end-customer will see
- many, many more...
Taking into consideration that all the steps above have many more mechanism that take place in order to make everything efficient and reusable and that all of this typically sit on top of frameworks we can see that what we want to do is actually a small part of what we actually do. Some say that the actual logic is below 2% of what is written. Most of the times there are transformations to different formats, integrations with 3rd party systems, presentation, persistence of data, e.t.c.
Of course all those layers are needed for various reasons but their implementation is driven by the logic that we want to implement. Next we will demonstrate a way to automate the creation of those layers
# Defining implementations
In our previous article we discussed how we can represent natural language descriptions to data structures. Having that as base we will define the parts that make one feature in the form of Definitions.
In order to simplify our process lets say that authentication and authorization is being handled and that the endpoints are just RESTFUL resources, meaning that for every feature we have the CRUD functionality covered. So with that we need to define:
- our model
- the request structure
- the response structure
- the hierarchy, meaning the relation between all this, how they are implemented together.
Let's see our Definitions and elaborate on them later
# model
<?php
namespace App\Definitions;
use j0hnys\Definitions\Definition;
final class Functionality extends Definition
{
const schema = [
"model" => [
"db_name" => "T::string()"
],
];
}
# request
<?php
namespace App\Definitions;
use j0hnys\Definitions\Definition;
final class Request extends Definition
{
const schema = [
"type" => '{{request_type}}',
"data" => [
"{{feature_property}}" => [
'type' => 'T::string()',
'validation' => [
'rule' => '{{validation_rule_string}}',
'message' => 'T::string()',
]
]
],
];
const request_type = [
'json'
];
const feature_property = 'T::string()';
const validation_rule_string = 'T::string()';
}
# response
<?php
namespace App\Definitions;
use j0hnys\Definitions\Definition;
final class Response extends Definition
{
const schema = [
"type" => '{{request_type}}',
"data" => [
"{{feature_property}}" => [
'return' => 'T::bool()',
]
],
];
const request_type = [
'json'
];
const feature_property = 'T::string()';
}
# hierarchy
<?php
namespace App\Definitions;
use j0hnys\Definitions\Definition;
final class Hierarchy extends Definition
{
const hierarchy = [
'{{feature_name}}' => [
'Resource' => [
'@\App\Definitions\Functionality',
'@\App\Definitions\Request',
'@\App\Definitions\Response',
]
],
];
const feature_name = 'T::string()';
}
There are a lot of definitions here but they can briefly explained as:
Our feature is a resource that consist of a functionality, request and a response
functionality points us to the persistent storage
the request consist of the structure of our HTTP request and validation rules
the response represents the structure and its type
To actually create code we will need a scaffolding generator. An example generator could follow the following structure.
<?php
namespace App\Definitions;
use j0hnys\Definitions\Definition;
final class ScaffoldGenerator extends Definition
{
const schema = [
'package' => [
'{{command}}' => [
'{{builder}}' => [
'{{stub}}' => '{{export}}',
]
]
]
];
const export = [
'{{feature}}' => '{{architecture}}'
]
const architecture = '@\App\Definitions\MVC';
const feature = '@\App\Definitions\Hierarchy';
const command = '@\j0hnys\Trident\Console\Commands';
const builder = '@\j0hnys\Trident\Builders';
const stub = './src/Stubs/*';
}
The @\App\Definitions\MVC
can be similar to the app's architecture from our previous article
We could summarize our generator as:
Our scaffold generator is a package that executes commands. Commands call builders which with the use of stubs, export features that follow a specific architecture
# Implementing with Definitions
The goal of Definitions is to set a specific set of rules that the implementation of the software that we build follows. The main mechanism to validate this is through checks. Whatever we implement we have to directly represent it (meaning with only code execution) to a data structure that can be compared to the Definition. For example:
The
$data = [
"type" => "json",
"data" => [
"name" => [
"type" => "string",
"validation" => [
"rule" => "required | string",
"message" => "name is required"
]
],
"value" => [
"type" => "integer",
"validation" => [
"rule" => "required | integer",
"message" => "value is required"
]
]
]
]
Can be checked against Definition App\Definitions\Request
. This structure can be made from a json file with this data or a database. Checking that this implementation is true means that we are implementing our feature correctly.
Let's say that we have the implementation details of one feature in json files. We have said that our feature consist of Model
, Request
, Response
which follow the appropriate Definitions. For example:
model.json
{
"model": {
"db_name": "demo_process"
}
}
request.json
{
"type": "json",
"data": {
"name": {
"type": "string",
"validation": {
"rule": "required | string",
"message": "name is required"
},
"fillable": true
},
"value": {
"type": "integer",
"validation": {
"rule": "required | integer",
"message": "value is required"
},
"fillable": true
}
}
}
response.json
{
"type": "json",
"data": {
"name": {
"return": true
},
"value": {
"return": true
}
}
}
Then we can feed this information to our Scaffold Generator and as described in the Definition above it will use commands, builders, stubs to export the generated code with a specific architecture. Of course the information will be first checked to see if it follows the specification.
# In conclusion
We can produce the scaffolding code of a feature, from three json files.
We can have multiple groups of those files, that each one produces one feature. All of those produce our Domain.
Using Definitions provides clarity of the internal workings of the software that is being build. As said previously Definitions describe the natural language descriptions to code readable format. This has additional benefits that will be described in a future article.
# Github
The concepts that have been described in this and the previous article are used in Trident and Vista. The goal of the packages is to build and manage the scaffolding that is needed in order to focus only on implementing business logic for Laravel applications.
Butler is a tool that has been build with Trident and Vista in order to make the management of applications made with these packages easier. It provides a UI which reflects the CLI commands of both packages.
links:
- Butler: https://github.com/j0hnys/butler
- Trident: https://github.com/j0hnys/trident
- Vista: https://github.com/j0hnys/vista