Comments (1)
Siren project still doesn't group all domain into its own domain package name, instead it stores all domain in a domain
package. If I remember last time the issue was import cycle. I have taken a look deep dive into the codebase and figure out the are some places where the code is highly coupled and don't have clear separation of concern.
Here are the points that could be improved.
Better passed an instance as dependency rather than initializing an instance inside a constructor (ref)
The code below shown we create NewSlackHelper()
and slack.NewService()
inside service constructor, this makes slackHelper
and slackService is hard to test. Even if we wire the dependency correctly, service does not need to know slackHelper
, its only dependency is slackService
.
// Service handles business logic
type Service struct {
repository store.ReceiverRepository
slackService slackService
slackHelper SlackHelper
}
// NewService returns service struct
func NewService(repository store.ReceiverRepository, httpClient http.Doer, encryptionKey string) (domain.ReceiverService, error) {
slackHelper, err := NewSlackHelper(httpClient, encryptionKey)
if err != nil {
return nil, errors.Wrap(err, "failed to create slack helper")
}
return &Service{
repository: repository,
slackHelper: slackHelper,
slackService: slack.NewService(),
}, nil
}
We can refactor the code to be like this.
type Service struct {
repository store.ReceiverRepository
slackService slackService
}
func NewService(repository store.ReceiverRepository, slackService SlackService) (domain.ReceiverService, error) {
return &Service{
repository: repository,
slackService: slackService,
slackHel
}, nil
}
and slackService
is
func NewService(repository store.ReceiverRepository, slackHelper SlackHelper) (domain.ReceiverService, error) {}
With the refactored code above, it is easier to mock the SlackService
and SlackHelper
and easier to test.
If we do this, we don't need to do this and we don't need to test like this.
All initializations should be done in the highest level (server creation) and pass down the instance to the lower components through dependency (ref)
All services, repositories, clients, etc should be created during server creation step (Except for some cases like singleton and lazy initialization). This will make a single source of truth of component creation and this would increase the predictability of our software.
If an error happened during a component creation, we could fail fast and fix the problem faster. Moreover, we don't have to deal with the scenario where component creation failure happen inside our business logic if we sure that all creation are handled earlier.
Don't mix all logic that interact with external parties inside our domain service (ref)
Here we can see there is cortex
client initialized and being used. Don't do this. Instead, we need to have a single package, for example cortex
(usually inside a pkg/cortex
) that does all interaction to external parties. Later, our domain service should only depend on the pkg/cortex
and this package can be used by any service in our domain. The cortex client can be initialized in the server creation and passed as dependency to the services.
Be more defensive in the code (ref)
There are a lot of scenarios that caused nil to be returned. Accessing nil in golang would cause panic. Panic is something that we should avoid. Gracefully handle the nil would make our code less possible to panic.
For example
clientId := configurations["client_id"].(string)
We need to check whether casting is fine. Even we can check if clientID is empty, we don't need to do external call, just error out.
In go function, if there is no output of the function, just return error only (ref)
This function return (*domain.SlackMessageSendResponse, error)
with
type SlackMessageSendResponse struct {
OK bool `json:"ok"`
}
This is ineffective, we can just make the function return error
only and check the OK inside the function.
Create a function as pure as possible
Non-pure function would make our software less predictable and would give you hard time to debug.
Avoid naming a variable with the same name like a package name in the project (ref)
subscription
is a package name in our project, naming a variable with the same name as a package name might not give issue for now. But later when the refactoring happen, there might be conflict with the package name and give more time to fix.
Avoid this issue by not using package name as variable.
Use blackbock testing to avoid import cycle and mimick the usage
Network is always unreliable, compensate this scenario
If we have a client that makes external call. Be sure to have a retry/circuit breaker/other resiliency logic.
Separating business logic and encryption logic (ref)
I notice in several places, we have some kind a "hook" to transform token plaintext to ciphertext in service layer. Seeing the usage, encryption & decryption are not really the business logic, they were tied to how do we store the data in repository. This is more or less the diagram.
We can make it better by separating the concern of encryption and business logic by introducing a kind of secure service proxy
like this.
Both service and secure service should conform the same interface. Doing so would make our code easier to test since we have the same mocks. There is also separation of concern with business logic and encryption logic.
from siren.
Related Issues (20)
- Notification Service Dispatcher
- Notification Source Idempotency
- Notification Handler
- Implement Postgres Queue between notification dispatcher and handler
- Templatize receiver details in receiver in Subscription of Notification Service
- Improve telemetry
- End-to-end test of notification service & updating the subscription flow
- Update Siren Documentation
- Add subscription client CLI
- Add server reflection for gRPC HOT 2
- Cortex client not passing tenant ID and namespace urn in update flow is always empty
- Add prometheus provider
- Siren SDK for notifications
- Parallelize notification handler goroutines
- Adding notification worker e2e test
- Add telemetry to notification workers
- Post alert with namespace API
- Sending alerts group as a notification
- Alerts & Notification Silencing
- Better alerts notification grouping
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from siren.