Skip to content

Conversation

mcmire
Copy link
Contributor

@mcmire mcmire commented Jul 22, 2025

Explanation

The controllers and services in sample-controllers are not as useful as examples as they could be. They do not follow the best practices that we want others to follow, so we cannot ask teams to copy them, and they do not follow patterns that are standard in the product.

This commit makes the following changes:

  • The SampleGasPricesService now makes use of the messenger system.
  • SampleGasPricesController and SamplePetnamesController now exposes their methods as actions, which follows the typical controller.
  • The AbstractSampleGasPricesService has been removed, and SampleGasPricesService is now expected to be used via the messaging system.
  • SampleGasPricesService now uses the data services pattern, specifically, createServicePolicy.
  • The tests for SampleGasPricesController and SamplePetnamesController now use the withController pattern.
  • The tests for SampleGasPricesController and SamplePetnamesController now set up a "unrestricted" messenger rather than a "root" messenger.
  • @metamask/network-controller types are now used directly instead of being copied (it was already a peer dependency).
  • fetchGasPrices in SampleGasPricesController is now called automatically when the globally selected chain changes, and also now takes a chain ID to match.
  • getDefaultGasPricesControllerState and getDefaultPetnamesControllerState are now placed in the "STATE" section.
  • getDefaultPetnamesControllerState is now exported.

References

Closes #6166.

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

The controllers and services in `sample-controllers` are not as useful
as examples as they could be. They do not follow the best practices that
we want others to follow, so we cannot ask teams to copy them, and they
do not follow patterns that are standard in the product.

This commit makes the following changes:

- The `SampleGasPricesService` now makes use of the messenger system.
- `SampleGasPricesController` and `SamplePetnamesController` now
  exposes their methods as actions, which is more typical of a
  conventional controller.
- The `AbstractSampleGasPricesService` has been removed, and
  `SampleGasPricesService` is now expected to be used via the messaging
  system.
- `SampleGasPricesService` now uses the data services pattern,
  specifically, `createServicePolicy`.
- The tests for `SampleGasPricesController` and
  `SamplePetnamesController` now use the `withController` pattern.
- The tests for `SampleGasPricesController` and
  `SamplePetnamesController` now set up a "unrestricted" messenger
  rather than a "root" messenger.
- `@metamask/network-controller` types are now used directly instead of
  being copied (it was already a peer dependency).
- `fetchGasPrices` in `SampleGasPricesController` is now called
  automatically when the globally selected chain changes, and also now
  takes a chain ID to match.
- `getDefaultPetnamesControllerState` is now exported.
@mcmire mcmire force-pushed the update-sample-controllers branch from 05b65fe to deac397 Compare July 22, 2025 19:27
@mcmire mcmire marked this pull request as ready for review July 22, 2025 20:52
@mcmire mcmire requested a review from a team as a code owner July 22, 2025 20:52
@mcmire mcmire added the team-wallet-framework Deprecated: Please use `team-core-platform` instead. label Jul 22, 2025
cursor[bot]

This comment was marked as outdated.

export {
getDefaultSampleGasPricesControllerState,
SampleGasPricesController,
getDefaultSampleGasPricesControllerState,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This export was already present, but it was not ordered alphabetically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file was added originally before sample-controllers was published to NPM as it wasn't a requirement that SampleGasPricesController be a working example, just illustrative. Since then, network-controller has been added as a peer dependency, so we can simply use the types from there.

*
* @returns The default {@link SampleGasPricesController} state.
*/
export function getDefaultSampleGasPricesControllerState(): SampleGasPricesControllerState {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this was added to the "MESSENGER" section originally (probably just an oversight). Regardless this now lives under "STATE".

`https://example.com/gas-prices/${chainId}.json`,
);
if (response.ok) {
// Type assertion: We have to assume the shape of the response data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (pre-existing issue): We can validate, we definitely don't need to assume the shape. Assuming the shape of the response without validation should be strongly discouraged. We've had plenty of examples even with internal APIs of them not returning the expected shape (especially with error responses, which sometimes can be returned even by HTTP 200 responses)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, but we haven't been validating responses elsewhere as far as I know. What is the pattern that we want to promote here, should we use Superstruct to do so or do it manually? And what should happen if the validation fails, should we throw an error?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any prescriptive guidance right now on how to best validate, so that would be up to the team or contributor.

Either throwing an error for invalid inputs, or stripping the data down to the valid parts/normalizing it, may be appropriate depending on the circumstance. Personally I'd default to throwing an error if I wasn't sure how to handle something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've added some basic validation here: 8f9a6b9

});

describe('assignPetname', () => {
describe.each([
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit convoluted 🤔 Is it helpful to test the method in both ways? If we test just the method, certainly the tests would miss a problem with the action registration. But if we have tests for the action, it's unclear what value the tests for the method have. Especially if we decide to disallow calling methods directly at some point in the future.

Copy link
Contributor Author

@mcmire mcmire Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have noticed that it is easy when adding an action which directly maps to a method to forget to test the action. (I notice, in fact, that I forgot to do this for SampleGasPricesService.) This approach seemed like the best way to ensure that both the method and the action do exactly the same thing.

But if you think this is convoluted, and you think that at some point we will disallow calling methods at some point in the future, perhaps we should promote a pattern where the action is tested comprehensively and the method is tested more simply? (We could of course just test the action, but that seems inconsistent, as there are some cases where actions don't map to methods, and the methods are still technically callable, at least for now.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the tests to primarily test the messenger actions here. Let me know if that fits more with what you were thinking or if you had something else in mind: b3f9796

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just seems like a lot for us to ask teams to test both redundantly, when there isn't really a reason for both to exist, you know. The way we wire up controllers, in theory nothing should be able to call any method directly anyway.

Certainly today there are methods that are not actions, but if this is an example of what we're recommending as best practices for new controllers, we need not let those existing inconsistencies restrict our choices here.

We haven't really agreed on preventing direct method calls though, that's just something I had in mind to propose in the future to simplify things. Maybe until we agree on that we should test both? I just didn't feel great about it, you know.

Copy link
Contributor Author

@mcmire mcmire Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know what you mean. We could certainly ask people to stop testing methods directly that are being exposed via the messenger. I worry that this guidance would be out of place at the moment, though. As it is, we do use methods directly, both in the background API and in Mobile. So at the moment, it is useful, even if it's not ideal. But I see your point. I'll think about this some more.

Copy link
Contributor Author

@mcmire mcmire Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the JSDocs to encourage people to use the controllers and service through the messenger, but left the method and its test in place for now since they have practical use: 0abf441. The tests for the methods are extremely simple on purpose, so it shouldn't be a huge burden if people were to copy those examples.

Once we have a plan for simplifying the background API as well as guidance for using the messenger from the UI, we should be able to completely drop methods along with their tests and promote the messenger as the one true way to use controllers and services.

Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks good! There is one significant problem with the service policy that must be fixed before merge, but most of my other comments are pre-existing issues or non-blocking discussions.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@Gudahtt Gudahtt self-requested a review August 21, 2025 21:07
SampleGasPricesServiceEvents,
SampleGasPricesServiceMessenger,
} from './sample-gas-prices-service/sample-gas-prices-service';
export type { SampleGasPricesServiceFetchGasPricesAction } from './sample-gas-prices-service/sample-gas-prices-service-method-action-types';
Copy link
Contributor Author

@mcmire mcmire Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These import paths are a bit annoying, now that I look at it. I'm curious whether flattening them is a good idea. Maybe I'll make another PR that organizes these files better.

@mcmire mcmire marked this pull request as draft August 21, 2025 22:12
@mcmire
Copy link
Contributor Author

mcmire commented Aug 21, 2025

The changes in #6335 will probably cause conflicts here, so I will wait until that's merged and then revisit this PR.

@@ -0,0 +1,24 @@
/**
* This file is auto generated by `scripts/generate-method-action-types.ts`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I noticed is that method action types for services are not generated automatically. I had to add this one myself. I will make a ticket to fix the script.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a ticket for this here: #6407

@mcmire
Copy link
Contributor Author

mcmire commented Aug 25, 2025

Ready for review again.

@mcmire mcmire marked this pull request as ready for review August 25, 2025 13:57
});
});

it.each([
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. This test is satisfyingly compact. Great to see response validation in the example service as well.

Gudahtt
Gudahtt previously approved these changes Aug 27, 2025
Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

*
* @returns The root messenger.
*/
function buildRootMessenger(): RootMessenger {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dang, I just noticed that this should be getRootMessenger to align with controller tests. I will change this tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now fixed: 7a97b62

@mcmire mcmire enabled auto-merge (squash) August 27, 2025 21:54
Copy link
Contributor

@cryptodev-2s cryptodev-2s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@mcmire mcmire merged commit 3bdf69c into main Aug 27, 2025
231 checks passed
@mcmire mcmire deleted the update-sample-controllers branch August 27, 2025 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
team-wallet-framework Deprecated: Please use `team-core-platform` instead.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The controllers and services in sample-controllers are not as useful as examples as they could be
3 participants