herbsjs / herbs Goto Github PK
View Code? Open in Web Editor NEWA domain-first framework. Code your domain and your infrastructure will follow
Home Page: https://herbsjs.org/
License: MIT License
A domain-first framework. Code your domain and your infrastructure will follow
Home Page: https://herbsjs.org/
License: MIT License
In this issue I would like to discuss usage ideas before implement it
Feature affected: Audit trail feature
New Performance Measurement APIs introduced in Node.js These APIs provide insights into the performance of applications.
This issue proposes the implementation of the Performance Measurement APIs in the AuditTrail module of HerbsJS, enabling developers to seamlessly monitor and measure the performance of their applications.
Benefits:
Enhanced Performance Analysis: Developers gain access to detailed performance data (CPU utilization, memory usage, event loop latency, function execution times), facilitating identification of bottlenecks and code optimization.
Real-time Monitoring: Developers can monitor application performance in real-time, enabling timely detection of degradation or anomalies, particularly beneficial for high-performance and responsive applications.
Optimization Opportunities: Performance data empowers developers to pinpoint areas for code optimization, resulting in faster and more efficient applications.
Use Scenarios:
Identifying Slow Routes: Performance Measurement APIs help identify routes or endpoints with longer processing times, allowing optimization for improved response times.
Optimizing CPU-bound Operations: Measure CPU utilization, identify CPU-intensive operations, and optimize algorithms or consider parallelization strategies for improved performance.
Monitoring Memory Usage: Track memory consumption, detect memory leaks, and optimize memory allocation and deallocation for efficient memory management.
Analyzing Event Loop Performance: Gain insights into event loop latency and execution times of event loop handlers, optimizing event-driven applications and ensuring timely event processing.
In conclusion, implementing the Performance Measurement APIs from Node.js 20v in the AuditTrail module of HerbsJS will empower developers to analyze and optimize the performance of their applications more effectively.
Audit trail example:
json
{
configuration:{output: {return: false, user: false}}
description:'A use case'
elapsedTime:362700n
request: null
steps: [ {
type: 'step',
description: 'A step',
return: {Ok: ''},
metrics: {...}
},
metrics: {...}
elapsedTime: 76100n
}],
transactionId:'cfd88c2b-1d34-4c81-a07c-ac4ea5420d04'
type:'use case'
}
Inside metrics we could collect everything we need from https://nodejs.org/api/perf_hooks.html
It could be activated by using an env var: APM=true npm run start
all the data could be provided in herbsshelf like this another issue requires #55
I'm thinking about the labels on issues and pull requests and would like to hear from you before implementing them.
Suggestions for issues and pull requests:
What do you think? Missed something? Overlabeling?
Hello everyone, seen that we need to construct some conversors, like camelcase conversor, or any other conversor like this Anchorme.js
, that converts string to an html with an anchor link, for example.
What is your perception about this theme?
Talking with @jhomarolo he do a suggestion to use the suma to realize the conversions, since some conversions like camelcase for example can be validated and used to another purpose and validations.
But in my opinion, we can create a new herb if we need, for me, the suma is something to improve de precision of our entities, and this conversor can be more generalist and do not have an accuracy too big, with the intention of improve versatility of the system.
And we can continue creating generic codes or using any js libs to do this conversions and transformations...
Is your feature request related to a problem? Please describe.
Working on a project, I spent a good couple of hours strugling with a mistake I made.
In a scenario where a property of an entity is an array of another entity, like the image below:
And that entity is the response of my usecase:
If I push an object that isn't an entity (does not implements BaseEntity
), it throws an error at runtime:
Describe the solution you'd like
It would be nice to have a better error message, telling the developer he is pushing the wrong object to that array.
The example I created is very simple, so it's easier to identify the problem. But when I faced this problem, I was on a larger code base, so it took me a lot of time to understand what was going on.
Describe alternatives you've considered
Herbs is written in JS, so it's hard to handle this kind of errors because it only happens at runtime.
Thinking about the problem, may it would be possible to identify this by using Proxies on entity properties, so we can intercept the changes in the object and print a more specific message on the console.
Additional context
I created a minimal herbs project showing how to emulate the same problem.
Today, transferring user data (I'll call it token) between usecases and sometimes even between applications always ends up requiring the creation of explicit variables in use cases, middlewares, manipulation classes, etc.
How can we define a convention or sometimes even some new functionality in Herbs for passing user tokens via context?
Is your feature request related to a problem? Please describe.
A message queue is a form of asynchronous communication between services used in serverless and microservices architectures. Messages are stored in the queue until processed and deleted. Each message is processed only once, by a single consumer. Message queues can be used to decouple heavy processing, to store work in buffers or batches, and to evenly process peak workloads.
In the modern cloud architecture, applications are decoupled into independent core components that are easier to develop, deploy and maintain. Message queues provide communication and coordination capabilities for these distributed applications. Message queues can greatly simplify coding decoupled applications and increase performance, reliability, and scalability.
Generally, messages are small and can be items such as requests, responses, error messages, or just information. To send a message, a component called a producer adds a message to the queue. The message is stored in the queue until another component called the consumer retrieves the message and does something with it.
Describe the solution you'd like
Considering the need to solve 2 main problems, I suggest we natively implement queue support in herbs.
problem 1
The unreliability of transmitting http requests between endpoints. That is, using the queue as a retry submission functionality (in a similar way to polly https://netflix.github.io/pollyjs/#/) .
problem 2
The big bottleneck that can be generated in massive data writing on the producer or consumer part. With the queuing system, both parties can process the request the way computational power processes requests.
Additional context
I imagine a solution implemented in some layer of buchu where I would mark that a usecase is consumer or producer.
usecase('Usecase X', **options**, {
// Input/Request type validation
request: { },
// Output/Response type
response: { },
// Authorization Audit
authorize: async (user) => {}
// Dependency Injection control
setup: (ctx) => {}
})
Where options could be a group of sets about producer: true, consumer:true, retrys: X
Changing a working software is hard. Especially when it is connected to a database.
Keeping the database schema in sync with the domain is timing consuming and error prone.
Herbs mantra is to make developers focus on the domain and let the tools handle the infrastructure code.
Herbs DB Migrations should help developers to keep your database schema in sync with your domain in a simple and easy way.
Ex: on herbs update
it should check if the database schema is in sync with the domain and if not, it should update the database schema.
The idea is to have a three phase process:
The VSD is a representation of the domain in a database schema. It should be generated from the domain entities.
It should handle the one-to-many and many-to-many relationships. It also should be agnostic to the database type.
Ex:
const User =
entity('User', {
id: id(Number),
name: field(String),
lastAccess: field(Date),
accessCount: field(Number),
hasAccess: field(Boolean),
plan: field(Plan),
groups: field([Group])
})
Should generate something like this:
const VSD = {
tables: [
{
name: 'users',
fields: [
{ name: 'id', type: 'number', isId: true },
{ name: 'name', type: 'string' },
{ name: 'lastAccess', type: 'date' },
{ name: 'accessCount', type: 'number' },
{ name: 'hasAccess', type: 'boolean' },
{ name: 'plan_id', type: 'number', fk: { table: 'plans', field: 'id' } },
]
},
{
name: 'plans',
fields: [
{ name: 'id', type: 'number', isId: true },
...
]
},
{
name: 'groups',
fields: [
{ name: 'id', type: 'number', isId: true },
...
]
},
{
name: 'users_groups',
fields: [
{ name: 'user_id', type: 'number', fk: { table: 'users', field: 'id' } },
{ name: 'group_id', type: 'number', fk: { table: 'groups', field: 'id' } },
]
}
]
}
The VSM is a representation of the database schema from the migration files. In the example above, it comes from the Knex migration files.
Example of Knex migration files:
exports.up = function(knex) {
return knex.schema
.createTable('users', function (table) {
table.increments('id')
table.string('name')
table.date('lastAccess')
table.integer('accessCount')
table.boolean('hasAccess')
table.integer('plan_id').references('id').inTable('plans')
})
.createTable('plans', function (table) {
table.increments('id')
...
})
.createTable('groups', function (table) {
table.increments('id')
...
})
.createTable('users_groups', function (table) {
table.integer('user_id').references('id').inTable('users')
table.integer('group_id').references('id').inTable('groups')
})
}
Instead of running the migration file using Knex and change the DB schema, we should implement the same functions / API exposed by Knex and generate the VSM.
Should generate something like this:
const VSM = {
tables: [
{
name: 'users',
fields: [
{ name: 'id', type: 'number', isId: true },
{ name: 'name', type: 'string' },
{ name: 'lastAccess', type: 'date' },
{ name: 'accessCount', type: 'number' },
{ name: 'hasAccess', type: 'boolean' },
{ name: 'plan_id', type: 'number', fk: { table: 'plans', field: 'id' } },
]
},
{
name: 'plans',
fields: [
{ name: 'id', type: 'number', isId: true },
...
]
},
{
name: 'groups',
fields: [
{ name: 'id', type: 'number', isId: true },
...
]
},
{
name: 'users_groups',
fields: [
{ name: 'user_id', type: 'number', fk: { table: 'users', field: 'id' } },
{ name: 'group_id', type: 'number', fk: { table: 'groups', field: 'id' } },
]
}
]
}
As you can see, the structure is the same as the VSD. It means it is agnostic to the database type. So in the future, we could support other libs like Sequelize or TypeORM.
Since we have the VSD as the desired state and the VSM as the current state, we can diff them and generate the new migrations.
There are some libs that can help us to do this diff. Ex: https://github.com/flitbit/diff
Once the diff format is generated, we can use the Knex API to generate the migration files.
As far as I know, there is no such tool that does anything similar to what I described above. Been able to change your domain and your database will follow is a game changer.
This is no small task and I bet there are lots of corner cases that would make this task harder than I described above. But I think it is feasible and worth the effort.
There are other issues and efforts related to this topic that I think are worth mentioning:
The reason I suggested this is beacuse it is more generic, works not only with new entities but also with existing ones: if you change a existing entity, it should generate the new migrations.
Is your feature request related to a problem? Please describe.
As a support platform for microservices and with the growing demand for serverless applications, it would be important for herbs to support the framework. Facilitating the configuration of routes and deploys, mainly on AWS.
Additional context
We already have a draft here: herbsjs/todolist-on-herbs#123
And an issue about it here: herbsjs/herbs-cli#75
Brainstorm from @dalssoft:
I've been thinking about the developer experience when dealing with glues. Although it is correct to have a folder and files for each glue (ex: graphQL and REST) from the architecture point of view, it doesn't make it clear the idea of metadata to developers.
Thinking about that I created a different way to specify the metadata:
(1) You would have a folder \infra\meta...
https://user-images.githubusercontent.com/209287/122963117-5e735200-d35c-11eb-996a-4e710618f243.png
(2) Meta for Entities
https://user-images.githubusercontent.com/209287/122962983-413e8380-d35c-11eb-99d1-cad70bd9f797.png
(2) Meta for Usecases
https://user-images.githubusercontent.com/209287/122963058-52879000-d35c-11eb-8315-19328a175fd1.png
Different approachs come to my mind but I think this one is the one I like most. What do you think?
Due to the deprecation of the underlying bash uploader, the Codecov GitHub Action has released v2 which will use the new uploader. You can learn more about our deprecation plan and the new uploader on our blog.
We will be restricting any updates to the v1 Action to security updates and hotfixes.
The v2 uploader has a few breaking changes for users
Multiple fields have not been transferred from the bash uploader or have been deprecated. Notably many of the functionalities and gcov_ arguments have been removed. Please check the documentation below for the full list.
More info: https://github.com/codecov/codecov-action
Is your feature request related to a problem? Please describe.
One problem is that herbsJs doesn't have type declaration files or JsDocs, which means we don't have intellisense in any of the libs or glues.
Describe the solution you'd like
We can insert .d.ts type declaration files to enable intellisense, at the same time these files become documentation.
This makes it easier to know which methods can be called and what they return.
Describe alternatives you've considered
We can also use DefinitelyTyped, where we can leave the type declarations in a separate repository, example: @types/herbs
Or add a JsDocs header to the functions we want to document.
Problem:
In order to use an use case or entity metadata or to know what the repositories of an application are, we first need to know where to find them.
The current solution are index.js
files with reference to all objects. Ex: \srs\domain\usecases\index.js
, \srs\domain\entities\index.js
and \srs\infra\repositories\index.js
These are the problems we are currently facing:
index.js
. This is a problem for glues.update
has difficulty keeping index.js
files up to dateAlso, some of these index.js
files carry metadata, such as index.js
for use cases (ex: { usecase: require('./user/createUser'), tags: { group: 'Users', type: 'create'} },
)
Suggestion:
Ideally there would be an automatic discovery mechanism for the objects, using their own metadata. In addition, it would be possible to add other metadata to the objects.
As an initial solution I imagined a solution where each UC or entity could "register" in a centralized list.
Ex:
// src\domain\entities\user.js
const User =
entity('User', {
...
discoveryservice.entities.add('User', User)
module.exports = User
// src\domain\usecases\user\createUser.js
const useCase = ({ userRepository }) => () =>
usecase('Create User', {
...
discoveryservice.usecases.add('CreateUser', useCase).metadata.add({ tags: { group: 'Users', type: 'create', entity: 'User' } })
module.exports = useCase
// src\infra\data\repositories\userRepository.js
const repository = class UserRepository extends Repository {
constructor(connection) {
super({
entity: User,
table: "users",
ids: ["id"],
knex: connection
})
}
}
discoveryservice.repositories.add('UserRepository', repository).metadata.add({ entity: 'User' })
module.exports = repository
With that a Herbs glue could easly find all the objects in a project to perform its goal.
Ex:
discoveryservice.findAll() // list of all objects
discoveryservice.usecases.findAll() // list of all usecases
...
If this discovery mechanism works, it could be argued whether module.exports
and require
should be used for this objects at all. It would be easier to just call discoveryservice.usecases.find('CreateUser')
because now it is not necessary to know the file path.
Regarding metadata, it would also be possible to add metadata for a object after its inital registration.
Ex:
discoveryservice.usecases.find('CreateUser').metadata.add({ rest: { httpVerb: 'get' } })
The suggested interface (method names, etc) for this solution needs refinement. But the general idea is here.
Given this, the nice thing would be to discuss all the premises presented here: if the problem is relevant, if this is the solution, etc. Please comment.
A framework, sails has a similar CRUD code generation, maybe it's a good example to help you develop and improve this part of the project!
as a contributor that spends time in all repositories(issues, pull requests), I would like to receive this thing in my issues and pull requests list on GitHub.
We could create a file for code owners that contain the nickname for all owners(herbs team + repository creator), the GitHub make the magic for notification us.
here we have a tutorial about it
Hello everyone!
I'm using react 16 with herbs and when I go to import the herbs module I get the herbs.csj file.
To Reproduce
Steps to reproduce the behavior:
import herbs from '@herbsjs/herbs'
Expected behavior
Import ES Module with herbs functions like entity, field...
Screenshots
The CLI has become a central tool in the Herbs developer experience.
The current CLI has two problems in my opinion: the first is technical, with low test coverage and with many responsibilities, making it difficult to evolve. The second is a DX (developer experience) which is ok, but not amazing.
But the CLI, like the website, is where developers perceive the quality of the project. In addition, differentiated DX can have a real impact on the developer's daily life and performance.
With that in mind, I'd like to bring the teaser to the contributors: the CLI should be AWESOME!
This means:
The developer using this CLI should feel like using something completely new, as well as feeling empowered to extract the maximum value from the tool. In short: developers need to love the CLI!
This could also be an opportunity to think about a new architecture that not only supports the evolution of the tool, but also brings concepts such as plugins, templates, etc.
I've been researching and doing some benchmarks and would like to share some here:
When I look at projects like Ink and Textualize, I see that there are still a lot of UI possibilities in the terminal that we are not exploring.
These tools open up a world of possibility from the point of view of usability and new aesthetics.
The possibilities are many!
https://github.com/Textualize/textual
https://github.com/projectwallace/wallace-cli
https://github.com/maticzav/emma-cli
https://github.com/GitGud-org/GitGud
https://github.com/kraanzu/gupshup
https://github.com/aristocratos/btop
https://github.com/jarun/googler
https://github.com/helix-editor/helix
https://github.com/chubin/wttr.in
https://github.com/rastapasta/mapscii
https://betterprogramming.pub/designing-beautiful-command-line-applications-with-python-72bd2f972ea
Let's not forget about images and animations!
https://www.youtube.com/watch?v=ZjN6L_btw0Q
https://asciiart.club/
https://ascii.co.uk/animated
https://www.deviantart.com/zonedev/art/Logotypes-zoNE-ASCII-3D-Animation-GIF-913308765
The aforementioned tools can greatly improve the usability of the CLI. In addition, we can apply best practices from other established CLIs that are loved by developers.
12 Factor CLI Apps
https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46
10 design principles for delightful CLIs
https://blog.developer.atlassian.com/10-design-principles-for-delightful-clis/
CLI Style Guide
https://devcenter.heroku.com/articles/cli-style-guide
Command Line UX in 2020
https://puppet.com/blog/command-line-ux-in-2020/
New DXs:
For example, what would it be like to bring herbsshelf to the CLI? Or what would a new experience for Aloe on the CLI look like?
Documentation:
The DX for documentation can be raised to the tenth power, be it the doc from the CLI, from Herbs and of course from the project itself using Herbs.
A new CLI is the opportunity to rethink the successes and mistakes of the past.
Responsibilities:
Currently there are responsibilities in the CLI that do not necessarily need to be in the CLI. Ex: Aloe test runner, complex and specialized file generation logics, etc.
What would an architecture that could be more modularized look like?
Tests:
By modularizing the CLI, it would be an opportunity to create more specific and decoupled tests, which could run in milliseconds, without file system dependency, for example.
For now I'm just putting everything in my head in terms of possibilities here. I believe that CLI can have a very high impact on the project and therefore deserves our attention and affection.
As next steps, the brainstorm is open to contributors. I intend to bring some ideas based on the content I brought here as well.
I would like to know what glue is in the alpha, beta, or mature version.
What about adding a status badge in all repositories?
I'm suggesting a new herbs glue for storing and fetching entities in an in-memory database
Initially, the idea is to build the glue for Memcached and then replicate it for Redis as well.
Here is the first (and not optimized) alpha version: https://github.com/jhomarolo/herbs2memcached/tree/main/testdb
I thought about replicating the methods (set, setmulti, get, getmulti) and still doing some abstractions like getAll and getbyId.
Additionally, I'm trying to resolve a known timeout issue in the Memcached library (3rd-Eden/memcached#199)
Feedbacks are welcome
Is your feature request related to a problem? Please describe.
Its a suggestion: create a glue for Herbs, to generate and make available spreadsheet files in XLSX format from the output data of the USE CASES processing.
Describe the solution you'd like
Create a glue for Herbs that generates from the output data of USE CASE processing, generates and makes available files in XLSX format ("Office Open XML" spreadsheet).
BENEFITS OF THIS GLUE:
Describe alternatives you've considered
The alternative is to generate XLSX spreadsheets manually, usually using one of the many libraries available.
Additional context
Some references about XLSX:
Describe the bug
The Step class uses JSON.stringify without the callback function to handle circular structures. When passing an object with a circular structure it shows the error "TypeError: Converting circular structure to JSON" and interrupts code execution.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The Setp class should perform the packaging of objects with circular structures.
Screenshots
Object with circular reference
Additional context
n/a
I think would be great if we could to use the herbs2rest metadata to generate a swagger page and a SDK-client for apis.
Following the @expresso router lib from @roziscoding, it receive a openAPIspecification and the swagger lib generates the documentation aaaaand the SDK-client.
thinking in herbsjs, would be great if I give the route matadata for a usecase or routes so it generates a swagger endpoint into my Express app.
const express = require('express')
const { generateSwagger } = require('@herbsjs/herbs2swagger')
const app = express()
const openAPISpecifications = {
"title": "Sample Pet Store App",
"description": "This is a sample server for a pet store.",
"termsOfService": "http://example.com/terms/",
"contact": {
"name": "API Support",
"url": "http://www.example.com/support",
"email": "[email protected]"
},
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0.1",
"routes": {
"get": {
"description": "Returns pets based on ID",
"summary": "Find pets by ID",
"operationId": "getPetsById",
"responses": {
"200": {
"description": "pet response",
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
}
}
}
}
},
"default": {
"description": "error payload",
"content": {
"text/html": {
"schema": {
"$ref": "#/components/schemas/ErrorModel"
}
}
}
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"description": "ID of pet to use",
"required": true,
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"style": "simple"
}
]
}
generateSwagger(app, routes)
we could use it together herbs2rest like
const express = require('express')
const { generateRoutes } = require('@herbsjs/herbs2rest')
const { useSwagger } = require('@herbsjs/herbs2swagger')
const app = express()
const routes = new express.Router()
const metadatas = generateRoutes(controllerList, routes, true)
useSwagger(app,metadatas)
app.use(routes)
Theme suggestions:
Is your feature request related to a problem? Please describe.
Buchu's audit trail functionality (https://github.com/herbsjs/buchu#audit) is something really illuminating. However, the tool needs an interface or integration that makes it possible to see in real time (or close to it) what is going on in the audit trail.
Describe the solution you'd like
I would like to see the audit trail logs somewhere.
From @italojs
What do you think about mutant test?
Some times ago we I had problems with herbs bugs that the unit test don't filtered it, so I was thinking how to mitigate it to happens again, so I would like to propose to use mutant test in herbs libs
The ideia of mutant tests is to test your test, confirm how much your test coverage all situations
example:
// ./src/legalAge.js
module.exports = isLegalAge = age => {
return age >= 18
}
// ./__tests__/legalAge.spec.js
test('FAIL: must to check if have legal age', () => {
expect(isLegalAge(17)).toBe(false)
})
the code above have 100% of test coverage, but what happens if some developer change the function code to
module.exports = isLegalAge = age => {
return age > 18
}
//or
module.exports = isLegalAge = age => {
return false
}
my test will pass and I dont will detec the new bug generated by this change because all my testes(100% coverage) is passing.
To solve it, the mutant test modify your code and try to broken your test, if the modified(bugged) code dont broken your tests, you must to improve your scenarios coverage
To fix the code above, the correct tests must to be:
test('FAIL: must to check if 17 is legal age', () => {
expect(isLegalAge(17)).toBe(false)
})
test('SUCCESS: must to check if 18 is legal age', () => {
expect(isLegalAge(18)).toBe(true)
})
test('SUCCESS: must to check if gratter then 18 is legal age', () => {
expect(isLegalAge(19)).toBe(true)
})
some refs:
[pt]
https://github.com/PauloGoncalvesBH/teste-de-mutacao/tree/at-talks
https://www.youtube.com/watch?v=k8Dq8YZgci8&ab_channel=Locaweb
[en]
https://pedrorijo.com/blog/intro-mutation/
http://atodorov.org/blog/2016/12/27/mutation-testing-vs-coverage/
https://medium.com/appsflyer/tests-coverage-is-dead-long-live-mutation-testing-7fd61020330e
Is your feature request related to a problem? Please describe.
With node 18 going to LTS now, we will need to plan to migrate applications to node 16, because it enters EOL (end of life) in April 2023.
We have to check if there are any compatibility problems but I think "we're good" to go ahead and upgrade node version of HerbsJS
Additional context
(https://nodejs.org/en/blog/announcements/v19-release-announce/)
As I have already discussed with some members of the Herbs community, I believe that in addition to the use case and the entities, the test scenarios are also part of the domain. This domain knowledge is usually translated from user stories, requirements, etc. to use cases as well as test cases. However, currently Herbs does not support to absorb this knowledge of test scenarios in a structured way.
When we think about how test scenarios are part of the domain, just think use cases are HOW, test scenarios are WHAT. Ex:
WHAT: Allow creating a client in the repository only if it is valid
HOW: Create Client (use case)
Or
WHAT: Do not allow changing a product in the repository if it is expired
HOW: Update Product (use case)
This knowledge is a fundamental part of the domain and should be discussed in a fluid way between developers, product managers, testers, etc. This knowledge should also have greater representation in the code in a structured way, as we have today with entities and use cases.
Given that, I propose a solution to capture this knowledge and expose it via metadata to other glues, especially Shelf.
Ex:
const { CreateProduct } = require('./usecases/createProduct')
spec(CreateProduct, {
'Successful create a simple product': scenario({
description: 'Try to creat a product with just a valid name.',
happyPath: true,
request: {
name: 'A simple product'
},
user: () =>
({ canCreateProduct: true }),
injection: () =>
({ ProductRepo: { save: () => { id: 1 } } }),
'Product must be saved in the repository': check((ret) => {
assert.ok(ret.product.id)
}),
'Product must have a new Id': check((ret) => {
assert.ok(ret.product.id === 1)
}),
}),
'Do not save a product with invalid name': scenario({
description: 'Reject products with invalid names in the repository',
request: {
name: 'A simple product @'
},
user: () =>
({ canCreateProduct: true }),
injection: () => { },
'Product should not be saved in the repository': check((ret) => {
assert.ok(ret.product === null)
assert.ok(ret.isInvalidEntityError)
}),
})
})
The first thing to note is that the spec
is connected to a use case. This is important because current test runners do not have this kind of explicit link with the objects to be tested. Here we want to capture the scenarios of a specific use case and have that as metadata. I see that the natural expansion would be to have spec
not only for use cases but also for entities, but I still haven't thought about how it would be.
Another important point, different from solutions like Cucumber, the code is close to the intent description.
When I started thinking about this lib, I was trying to emulate the BDD (behavior driven development) in the spec
structure, but it became clear that some things are already given in the current structure of Herbs and would not need to be rewritten.
The Given is basically the input and is formed by the set of:
injection
for dependency injection.user
for use case authorization;request
and its values;The When is the execution of the use case, going through the authorization first. But this is transparent and happens automatically.
const uc = usecase(injection)
const hasAccess = await uc.authorize(user)
const response = await uc.run(request)
What we're left with is Then. Here the idea is not only to verify the output of the use case, but also to capture domain knowledge, explaining what is valid and what is not valid for each scenario.
One more example:
spec(ChangeItemPosition, {
'Successful change item position': scenario({
description: 'Try to change the item position in a list with a valid position.',
happyPath: true,
request: {
itemId: 1,
position: 10
},
user: () =>
({ canChangePosition: true }),
injection: () =>
({
ListRepo: { find: (id) => ({ id, items: [] }) },
ItemRepo: { save: () => ({ position: 10 }) }
}),
'Item position must have been changed and saved in the repository': check((ret) => {
assert.ok(ret.item.position === 10)
}),
'Item position must be in a valid range': check((ret) => {
assert.ok(ret.item.position >= 0)
assert.ok(ret.item.position <= 20)
})
}),
'Do not change item position when a position is invalid': scenario({
description: 'A position for a item is invalid when it is out of range',
request: {
itemId: 1,
position: 100
},
user: () =>
({ canChangePosition: true }),
injection: () =>
({
ListRepo: { find: (id) => ({ id, items: [] }) },
ItemRepo: { save: () => ({ position: 10 }) }
}),
'Item position should not be changed in the repository': check((ret) => {
assert.ok(ret.isInvalidPositionError)
}),
})
})
Having a structured form of the scenarios is important for the developer to have clarity of the scenarios that that use case is exercised.
But perhaps just as important is that we can extract this knowledge from the code and bring it to the conversation with stakeholders. For that the Shelf would be the ideal tool.
Using the use cases above as an example, it would be possible to build documentation something like:
Create Product
Change Item Position
Every spec
should be informed to Herbarium as a new kind of object.
module.exports =
herbarium.specs
.add(CreateProductSpec)
.spec
It would be possible to find specs related to a use case or entity using Herbarium.
One functionality that needs to be discussed is the multiple inputs to validate the same scenario. I think of something like this:
spec(CreateProduct, {
'Successful create a simple product': scenario({
...
request: [
{ name: 'A simple product' },
{ name: 'ProdName' },
{ name: 'A simple product' }
],
But it needs to be discussed how each check
has context about which request
item is being executed. Maybe use ctx
instead of just ret (check((ctx)
) and use ctx.ret
and ctx.req
, as in the use case, with ctx.req
containing info about request of that execution.
Advanced scenarios with spys should be allowed because in some cases it is the only way to validate the output. However, the use of mocks should be discouraged, as they validate and couple to the behavior of the use case and not to the output.
We need to dig deeper to see how these scenarios would look.
This is the beginning of a discussion. Conceptual and implementation insights are welcome. It would be great if someone can bring examples so that we can exercise this model as well.
As we are using practically all herbs projects with winston + kinesis, with code repetition in several places, I imagined that we can create a new herb glue that automatically connects herbs with winston (and still remains compatible for winston connectors ), possible a herbs2winston glue;
I would like to know if you think that there would be a real gain in this implementation and how it could work (breaking as little as possible of what we have today)
Add: Imagine just like the buchu that you can put in .env the audit for it to blow the exception, we can put this glue to log the entire use case execution that happens, I don't know, it was just an idea.
Add: I imagine the lib going more towards, for example, automatically auditing all usecases with a predefined (winston) transport. Or log all usecases that return error automatically.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.