GithubHelp home page GithubHelp logo

Refactor mock files and codes about siren HOT 1 CLOSED

raystack avatar raystack commented on July 30, 2024
Refactor mock files and codes

from siren.

Comments (1)

mabdh avatar mabdh commented on July 30, 2024 2

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.
Untitled Diagram-Page-1 drawio

We can make it better by separating the concern of encryption and business logic by introducing a kind of secure service proxy like this.
Untitled Diagram-Page-2 drawio
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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.