Basic idea: the application (not the domain model) is responsible for fetching data from the world outside the model.
To generate a Membership ID, I need to take a random string and the timestamp of when the user account created. Then, those two values are sent to external REST API and the success response of it will contain the MembershipID.
In effect, you have something that looks like a function, that takes a random seed and a timestamp as an argument, and returns a MembershipId.
That function is going to be implemented by application components that know how to speak REST, where the external api is hosted, and so on.
That function is going to be invoked, typically, one of two ways. Either the application is going to do the work, and then pass the value to the domain model...
m = membershipId(domainModel.randomSeed, domainModel.createdTime)
domainModel.assignMembershipId(m)
OR, we'll pass the capability into the domain model, and let the model to decide whether or not to invoke it:
DomainModel::doIt(membershipId) {
this.membershipId = membershipId(this.randomSeed, this.createdTime)
}
The difference between the two approaches is largely in how you handle failures. In the first approach, all of the error handling code lives outside the domain model, which only receives values when they are available. The second answer lends itself to a more procedural style - you just use the callback provided by the application layer whenever you want to - BUT you have to pay a lot more attention to error handling when you are working in the "domain" code.
I find that the former style - where all of the complexities relating to the fact that the external API may not be available when we want - is more in keeping with the DDD approach of separating the domain code from the plumbing. In effect, the domain model becomes a simple in memory state machine that does bookkeeping, and the application interfaces with all of the plumbing concerns.
right now I put the external API accessing logic inside domain service
Using the upper style, you would implement the accessing logic in an application service, not in a domain service. Getting the data from "somewhere else" is not a domain modeling concern, but a plumbing concern.
The second style, where we pass the capability into the domain model to be invoked, is analogous to using a domain service. And you will see that style in a lot of examples, because people tend to apply patterns to match what they usually do, rather than adapting their style to match a new pattern.