Replies: 3 comments 2 replies
-
|
As a first feedback I think your solution looks great! For me though, the premise of Github being at the very core of the application and coupled with implementations didn't sound so bad, because at the end it was very central to the purpose of the project and even its name. If we wanted to get data from other sources like Gitlab, we would have to even change the model itself (different concepts merge requests vs pull requests...) and the application would be different in its objectives. But if we look at it from a extensibility standpoint, it makes sense to be able to adapt to different sources. At the end, we don't know how much the requirements could change in the future, so it's nice to have. I'll have a deeper look later! |
Beta Was this translation helpful? Give feedback.
-
|
After reading the article I also agree that the @mikededo proposal looks great! Nevertheless, while i share same opinion on some aspects I don't quite see the approach of the Under my point of view, there shouldn't be a When the user introduces the following sequence of commands: The point of hexagonal design on here for me would be if we change the project design and decide that GitHub data is stored on MySQL. Then, since we used the ports and adapters approach, we would only have to change the repository adapters which are passed to the usecase constructors in the If in the future we are asked to develop a feature that can handle: About the DTOs placement, i think that DTOs sould belong to the application layer, as they shouldn't be coupled to the db/external repository implementations but to the use cases we want to call from the Controllers. Notice that this is my point of view and I completely agree to discuss it all on a meeting later. Thank you @mikededo for your in-depth analysis. |
Beta Was this translation helpful? Give feedback.
-
|
In first place kudos to @mikededo for such great analysis and the initiative to post the discussion. There are good points from all of you. I'll try to add mine. In the current context and knowledge, we all agree that doesn't make sense the proposal I made with moving GitHub and DB controllers to Every infrastructure has to have its own controllers, and as @manerow has noted it doesn't make sense to overcomplicate and make a controller of controllers. We all agree that GitHub and DB controllers implement the same logic to retrieve data, not the implementation detail. We should make a contract that both pieces must implement. GitHub controller has a difference: it also has to persist the data when fetching is finished. Mixing the hierarchy proposed by @mikededo, I would like to add my view on how it should be, I'll mark with ** the additions githubstats
├── application
│ ├── exceptions
│ ├── discount
│ ├── mapper
│ └── ** use case
│ ├── ** fetch
│ ├── ** get
│ └── ** save
├── domain
│ ├── exceptions
│ ├── repository
│ ├── ** entity
│ └── ** services
└── infrastructure
├── github
│ ├── controller
│ └── repository
├── postgres
│ ├── ** repository
│ └── controller
└── shell
├── components
├── settings
├── controller
├── model
└── validationI will start with easy throughs about this:
At this point of the project we have set a basis of code, and we don't need any more enablers to rapid-develop. I'm referencing JPA and lombok:
I propose that, At GitHub - External services point 3, @mikededo suggestes applying the dependency inversion principle and injecting the Repository interface and is totally right. Here I just to note that we need to set bidirectional relations manually when we fetch data from Github, because when persisting an entity if its integrity doesn't match, the entities will not be relational. This is not currently done.
We need to create a Proxy design pattern, that should be called by the shell, and it will encapsulate the logic to decide which collaborator to call (internal, external) in the function if the request is already at the historic table. What @mikededo said about DTO and mappers is right: entities are dirty with plenty of mixing of annotations from persistence ( DTOs are coupled to the implementation, and their name should reflect it too. @sdomingobasora is right when saying this app is github-stats and isn't our objective to have flexibility in this matter. We don't need to overcomplicate without specific requirements. But also I agree with @manerow about the placement of DTOs should be at the application layer, because they are not domain entities, just representations that will be used with the superior layer ( |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
🗃️ Changes to our file structure
📝 Description
This last class we discussed some issues with the teacher and I came to the conclusion that we do not have a proper folder structure, and that there are some things that have to be changed. This also affect the current PR in progress which will add the database controller (see #102).
❗ Problem
The entrypoint of our application (as of now) is the
infrastructure.shellpackage. This package has, as of now, one controller, theUserOptionControllerthat currently is responsible of handling the execution when theuseroption is selected. It creates the requiredGitHubRepositories, which are implemented underinfrastructure.github.repositoryand it handles all the logic.The bad decision in this case is that we directly coupled the controller to the
GitHubimplementation. This means that, if by any chance we allowed the user to choose if they want to fetch the data fromGitHuborGitLab, we would not be able to reuse the currentUserOptionController, since it is tightly coupled with theGitHubREST API implementation.💡 Proposal
🌐 GitHub - External services
First, we have to decouple the
UserOptionControllerfrom theGitHubREST API. In order to do this, we have to:infrastructure.githubnamedcontrollerthat contains the logic of theUserOptionControllercoupled to theGitHubREST API. In this case this is not an issue since it is inside theinfrastructure.githubpackage, and it has to be coupled to such implementation.infrastructure.shell.controllerthat handles theUserOptionwill have to call theGithubUserOptionControllerwhich will run the code that fetches data from the GitHub API.infrastructure.githubknow about the database we are using. With @manerow, we thought about injecting theGithubUserOptionControllerwith a use case that will allow the controller to save the data. Furthermore, thanks toJPA, we would only need to save an organization, as it will cascade save the other entities.If there's ever the need to add a new external service, for example fetching data from the GitLab API, we would just have to replicate the package structure from
infrastructure.github(implementing the required repositories for the use cases that it uses and theGitLabUserOptionController). Then, inside theUserOptionControllerinside theinfrastructure.shellpackage, we would choose, depending on the user input, one controller or the other. Here's an example:💾 Database
A similar thing happens with the databases. We currently have a PostgreSQL database, but maybe in a future we add a microservices structure and user's data is stored in a PostgreSQL database while team's data is stored in a MySQL database. Our incoming implementation (#102) does not differentiate the database that we are using since it adds everything inside the
infrastructure.controllerfolder.The solution would be similar to the external services approach:
infrastructurefor each database system. For example, we would now move theinfrastructure.controllerpackage insideinfrastructure.postgrespackage.JPA). We will also implement the controllers for the different options that our application has or that we want our database pacakge to be able to have. For example, thePostgreslUserOptionControllerandPostgresTeamOptionController.infrastructure.shellpackage, we would decide on which database system we choose regarding the user situation.🗺️ DTO's and mappers
Another issue that we have is regarding our entities and our DTO's. Initially, we had defined DTO's inside the
application.dtopackage. Furthermore, we had implemented the mappers that would convert theDTOto a domain entity and vice-versa (if needed of course). However, we found ourself working both with DTO's and domain entities inside the controllers, which was the reason why we decided to move our JSON translation (mapping) to our entities inside the domain layer. With this, we were also excluding the need of mappersThis was a terrible decision as we were coupling information that should only be know on the
infrastructurelayer, inside ourdomainlayer. This is breaking all the rules about our intend to apply hexagonal architecture to the project.Now, in order to fix this, we have to implement the DTO's outside the
domainlayer (obviously) into ourinfrastructurelayer. The reason on why I think that the DTO's should be inside theinfrastructurelayer is because the JSON mapping will depend on the REST API impementation and therefore theJsonPropertyannotation that we are using will be different for every service that we are using. Here's an example:By creating different classes we allow ourselves the possibility of having different
JsonPropertyassignations, simplifying the logic.With this decision, another problem arises and it is the mappers. For mappers, we could also have the same approach and have a
GitHubPullRequestMapperand aGitLabPullRequestMapperwhich will convert theDTO's to the expected entity.However, I believe that we can take a better approach and define a contract for the
DTO's. This contract will be used inside the mappers to create the entity without knowing the specific implementation of the DTO. Let me first explain my steps and later I will provide an example:PullRequestDTOinterface that will define a set of methods (getters) that the specific implementation for theDTOwill have to implement. This methods will be used to obtain data from theDTO.DTO(GitLabPullRequestDTOorGitHubPullRequestDTO) will implement suchDTOcontract.PullRequestDTOand it will convert such argument to aPullRequestentity.The exemple would be, using the previously defined
DTO's:🎓 Conclusion
After all that I have discussed, if we all agree, we should come up with a solution/decision and start migrating/updating our code to the decided solution! ✌🏼
The folder structure, after applying the changes, would look as:
io/packland/mdas/githubstats/ ├── application/ │ ├── exceptions │ ├── dto │ ├── mapper │ └── ... ├── domain/ │ ├── exceptions │ ├── repository │ └── ... └── infrastructure/ ├── github/ │ ├── controller │ ├── dto │ └── repository ├── postgres/ │ └── controller └── shell/ ├── components ├── configuration ├── controller ├── model └── validationBeta Was this translation helpful? Give feedback.
All reactions