1. DDD
The code is open source: github.com/YituHealthc…
DDD from Getting Started to Giving up requires only a brain map
It is a strange phenomenon that DDD articles usually only talk about ideas and concepts, not implementation and code. “Ideas, ideas”, or is DDD only responsible for the early design.
Because they don’t have intuitive code use cases like design patterns, and they have a lot of unintelligible concepts, they often have trouble landing. This paper is based on the author’s understanding of DDD, recording technology selection process and radical code explanation, slot point too much, welcome to communicate.
1.1 What is DDD
Domain-driven Design (DDD) drives Design. It refers to the modeling of the domain involved in the software to deal with the problem of software complexity when the system scale is too large.
1.1.1 SIMPLE DDD Example
Try a simple example to illustrate
If I wanted to update a user’s password or name or age, from a data-oriented perspective, I would have a table of users. All of the above operations are updates to the user table. I could design this as userService.save (user) and implement some repetition restrictions with key value constraints. However, when the method is only seen in subsequent iterations, without understanding how it is used externally, the method quickly fails to continue iterating.
Instead of focusing on entities and value objects, we try to do this through a semantically explicit API. So I can provide N methods
UserService.updatePassword(password, userId);
UserService.updateName(name, userId);
UserService.updateAge(age, userId);
This code already fits well with the Spring+ anemic model-based style
Let’s revisit the original requirement, update the User password or User name or age. Should this be done by the User? Such as
User.updatePassword(password);
User.updateName(name);
User.updateAge(age);
This is DDD-style code, written with the logic that best fits the business and positive thinking. It would be better if we didn’t have to worry about data persistence and how to use it with Spring. Of course, if we had a server with unlimited memory that never crashed, we could do the same.
1.1.2 What is DDD not
DDD is not a silver bullet.
If it were a silver bullet, or if it worked for most scenarios, DDD would have gone mainstream long ago (on the other hand, because of the engineering requirements). However, DDD is a suitable solution for certain scenarios.
1.2 Why use DDD
In a field of innovation and exploration, or a field with a high threshold of industry, it is too one-sided from the engineering perspective, and experts in the field need to be introduced to work together.
There are a couple of problems
- How do people from different professions, using different tools, and speaking different terms work together? How do you make sure your ideas are aligned?
- How to deepen business understanding and more accurate definition, so as to achieve business deep exploration and exploration?
- How does modeling knowledge sink? Is there a more general and efficient way to spread data in 10bit/s by word of mouth among business experts?
- How to reduce the cost of frequent changes during business exploration? No need to translate between business modeling, architectural design, and coding?
Perhaps DDD can solve the above problems.
Consider: If an industry is relatively mature and stable and already has well-known products, isn’t it better to “borrow” their solutions directly?
1.3 How to Use DDD
Usecase discuss as A usecase design as B usecase practice as C usecase overturn as D a-right -> B b-down -> C c-left -> D d-up -> ACopy the code
- Through user stories, each person defines related “domain practices”, “actions”, and “user personas” according to their own understanding, and the structure format is
actor->command->event
, that is, someone performed an action that resulted in an event
- Define the common language (the common language created by the team). Based on the above definition, make clear common sense and concepts by summarizing and giving definitions, and finally output the business dictionary table. Follow-up communication and discussion through common name
- A generic language needs to have a Bounded Context, a common sense name that represents meaning only within the Context. The same concept in different contexts represents different meanings and has different attributes and behaviors
- The alignment process includes but is not limited to
- discuss
- The resources
- Reference standard
- Consult your dictionary
- The sample
-
Project: Work done to achieve a product iteration, product module development, etc.
-
Milestone: Indicates the time and goals of a Project. A Project can have multiple milestones at the same or different stages at the same time.
-
- throughEventStormingThe user story is abstracted as a “sticker domain map” and aggregated according to the aggregation root
- This process and the common language definition can go hand in hand and be clearly defined during the discussion
- For details on EventStorming, visit www.eventstorming.com
The above process is relatively general, but in my practice, I have added another step
- Write according to the aggregated “sticker field map”
GraphQL Schema
2. GraphQL
2.1 What is GraphQL
graphql.org/
A query language for apis
GraphQL is both a query language for the API and a runtime for your data queries. GraphQL provides an easy-to-understand set of complete descriptions of the data in your API, enabling a client to get exactly what it needs without any redundancy, making it easier for the API to evolve over time, and for building powerful developer tools
2.2 Why GraphQL
How to ensure simultaneous changes between domain models and practices?
“If we have a machine with unlimited memory that never crashes,” we can easily develop DDD-compliant code, which is the domain layer of our code. But in the real world, not only do we not have this machine, but we have to interact with all kinds of things, like the UI, to provide relevant data. At this point, according to the hexagonal orCA architecture, we extend one of the edges.
This is where the problem arises. Once we have drawn the entities and connected the various relationship lines, we have to flatten them out and do various transformations (from the domain layer to the API interaction layer, the front-end also does the same various xxObject transformations, but we are developing under the same set of jointly created domain models). This process is called API definition.
If the front end can manipulate the data through the domain model, can you skip this process? To find a balance between the two, GraphQL Schema describes the domain model and interfaces at the same time.
2.3 How to use GraphQL
The examples on the web are mostly in-memory data, not demos that use databases, whether relational or NOSQL. The implementation is not elegant, dragging down the GraphQL experience
@Component
public class GraphQLDataFetchers {
private static List<Map<String, String>> authors = Arrays.asList(
ImmutableMap.of("id"."author-1"."firstName"."Joanne"."lastName"."Rowling"),
ImmutableMap.of("id"."author-2"."firstName"."Herman"."lastName"."Melville"),
ImmutableMap.of("id"."author-3"."firstName"."Anne"."lastName"."Rice"));public DataFetcher getAuthorDataFetcher(a) {
return dataFetchingEnvironment -> {
Map<String,String> book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null); }; }}Copy the code
Based on the previous reasoning, we have already described the domain model and API in schema, so can we extend it further to describe persistence information?
3. Dgraph
3.1 What is GraphQL
Dgraph is an open-source, scalable, distributed, highly available and fast graph database, designed from the ground up to be run in production.
3.2 Why Dgraph
- The graph database is more intuitive and conforms to the process of domain design, such as adding users to a project, which is really just a link between the project and the user
belong
The line - Support GraphQL
3.3 How to use Dgraph
- Github.com/dgraph-io/d…
// Query
String query =
"query all($a: string){\n" +
" all(func: eq(name, $a)) {\n" +
" name\n" +
"}\n" +
"}\n";
Map<String, String> vars = Collections.singletonMap("$a"."Alice");
AsyncTransaction txn = dgraphAsyncClient.newTransaction();
txn.query(query).thenAccept(response -> {
// Deserialize
People ppl = gson.fromJson(res.getJson().toStringUtf8(), People.class);
// Print results
System.out.printf("people found: %d\n", ppl.all.size());
ppl.all.forEach(person -> System.out.println(person.name));
});
Copy the code
I think the only thing the official example does is show that Java doesn’t work, so learn to Go.
4. Domain events
Subscribe to domain events through message queues for asynchronous interactions.
5. Arc
The above joke and dug so much, because hope to use a set of framework to solve the problem, let DDD landing operation more smooth.
Arc integrates & implements many functions
As a whole
- Integrated Zipkin link tracking
- Plug into Spring and simplify operations by configuring + annotations
GraphQL level
- Embedded playground & Voyager is convenient for debugging and sorting out domain relations
- Custom exception handling
- Intercept custom interceptor
- GraphqlClient is provided for GraphQL calls between servers
- Automatically resolve schema defined types, union types, and interface types
- Encapsulate the Controller layer, scan and parse @DatafetcherService and @Graphqlmutation and @GraphqlQuery methods
Dgraph level
- Intercept custom interceptor
- Configure database information using properties
- Automatically scan xxxdgraph.xml, support writing complex statements, and provide static and dynamic variable handling
- Provides RDF processing tools
- SimpleRepository provides basic operations such as Save, getOne, getAll, Relationship, and upsert
mq
- Vm-based lightweight message queues
- Intercepts and sends domain messages based on subscriptions
5.1 Development Process
The overall development process after adding the Arc framework via Maven:
start
: create graphql.schema & dgraph.schema;
: create javaBean with \n @DgraphType \n @UidField \n @RelationshipField;
: create @Repository class extends SimpleDgraphRepository;
: add xxDgraph.xml for complex persistence handle;
: create @DataFetcherService class include \n @GraphqlQuery \n @GraphqlMutation;
: browser http://localhost:${port}/playground for debug;
end
Copy the code
- Define the domain model and generate graphqL. schema and dgraph.schema files.
- Create a javaBean and specify @dgraphType, @uidField, and @Relationshipfield
- Create a subclass of SimpleDgraphRepository and declare it @Repository
- Write xxdgraph. XML to realize query method to realize complex DB operation
- Create @DatafetcherService and @graphqlQuery and @Graphqlmutation methods
- Go to http://localhost:${port}/playground to debug
5.2 the Sample
graphql schema
scalar DateTime schema{ query: Query, mutation: Mutation } type Query{ project( id: String ): Project users: [User] } type Mutation{ createProject( payload: ProjectInput ): Project createMilestone( payload: MilestoneInput ): Milestone} """ ProjectCategory """ enum ProjectCategory {""" sample project """ DEMO """ "PRODUCTION project """ PRODUCTION} "" name Work done for the purpose of a product iteration, product module development, or research. name: String! description: String! category: ProjectCategory createTime: DateTime! Renamed (status: Milestone): [Milestone]} "" The Milestone expresses a certain time stage and phased objective of a Project. A Project can have more than one Milestone{id: String! """ type Milestone{id: String! name: String! status: MilestoneStatus } type User { name: String! } """ "MilestoneStatus """ enum milestone {""" not started """ NOT_STARTED, """ in process """ DOING, """ "released "" RELEASE, """ "CLOSE "" CLOSE} INPUT ProjectInput{name: String! description: String! vendorBranches: [String!] ! category: ProjectCategory! } input MilestoneInput{ projectId: String! name: String! }Copy the code
Java field
@Slf4j
@DataFetcherService
@DgraphType("PROJECT")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Project {
private static final String RELATIONSHIP_HAS = "has";
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
@Autowired
private ProjectRepository projectRepository;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
@Autowired
private MilestoneRepository milestoneRepository;
@UidField
private String id;
private String name;
private String description;
private ProjectCategory category;
private OffsetDateTime createTime;
@RelationshipField(RELATIONSHIP_HAS)
private List<Milestone> milestoneList;
@GraphqlMutation
public DataFetcher<Project> createProject(a) {
return dataFetchingEnvironment -> {
ProjectInput input = GraphqlPayloadUtil.resolveArguments(dataFetchingEnvironment.getArguments(), ProjectInput.class);
OffsetDateTime now = OffsetDateTime.now();
this.name = input.getName();
this.description = input.getDescription();
this.category = input.getCategory();
this.createTime = now;
this.id = projectRepository.save(this);
return this;
};
}
@GraphqlMutation
public DataFetcher<Milestone> createMilestone(a) {
return dataFetchingEnvironment -> {
MilestoneInput input = GraphqlPayloadUtil.resolveArguments(dataFetchingEnvironment.getArguments(), MilestoneInput.class);
// You can create a milestone forward through project, or you can create a milestone first and associate it with a project
// this.id = input.getProjectId();
// this.milestoneList = Collections.singletonList(new Milestone(input.getName()));
// this.id = projectRepository.save(this);
Milestone milestone = new Milestone(input.getName());
milestone.setStatus(MilestoneStatus.NOT_STARTED);
String milestoneId = milestoneRepository.save(milestone);
milestone.setId(milestoneId);
milestoneRepository.createRelationship(RelationshipInformation.builder()
.sourceList(Collections.singletonList(input.getProjectId()))
.relationship(RELATIONSHIP_HAS)
.targetList(Collections.singletonList(milestoneId))
.build());
return milestone;
};
}
@GraphqlQuery
public DataFetcher<Project> project(a) {
return dataFetchingEnvironment -> {
String id = dataFetchingEnvironment.getArgument("id");
return projectRepository.getOne(id)
.orElse(null);
};
}
@GraphqlQuery(type = "Project")
public DataFetcher<List<Milestone>> milestones() {
return dataFetchingEnvironment -> {
Project project = dataFetchingEnvironment.getSource();
List<Milestone> milestones = milestoneRepository.listByProjectId(project.id);
String status = dataFetchingEnvironment.getArgument("status");
if (StringUtils.isEmpty(status)) {
return milestones;
} else {
MilestoneStatus milestoneStatus = MilestoneStatus.valueOf(status);
returnmilestones.stream() .filter(m -> m.getStatus().equals(milestoneStatus)) .collect(Collectors.toList()); }}; }// Just to show the merge request
@GraphqlQuery
public DataFetcher<List<User>> users() {
return dataFetchingEnvironment -> Arrays.asList(
User.builder().name("u1").build(),
User.builder().name("u2").build(),
User.builder().name("u3").build()
);
}
@Consumer(topic = "users")
public void usersListener(Message<DomainEvent> record) {
log.info("listen users event: {}", record); }}Copy the code
repository
@Repository
public class MilestoneRepository extends SimpleDgraphRepository<Milestone> {
public List<Milestone> listByProjectId(String projectId) {
Map<String, String> vars = new HashMap<>();
vars.put("projectId", projectId);
return this.queryForList("milestone.listByProjectId", vars); }}Copy the code
MilestoneDgraph.xml
<dgraph>
<var id="type">
MILESTONE
</var>
<var id="common">
uid
expand(MILESTONE)
</var>
<query id="listByProjectId">
query listByProjectId($projectId: string) {
var(func:uid($projectId)) {
has {
var mids as uid
}
}
listByProjectId(func:uid(mids)) {
uid
expand(_all_)
}
}
</query>
<mutation id="updateStatus"><! [CDATA[ <$id> <MILESTONE.status> "$status" . ]]></mutation>
</dgraph>
Copy the code
5.3 the effect
localhost:8080/playground
5.4 Follow-up Plan
- Generate relevant code automatically from schema
- Parse GraphQL to dynamically generate Dgraph queries
Disadvantages 6.
- Interactions between domains must be via GraphQL, and client encapsulation is not elegant enough
- Dgraph is not good at statistical calculations, such as statistics classes that store metrics via mysql based on AOP+MQ
- Both front and back end technology systems change simultaneously
7. Recommended tools
7.1 tools
Useful tools are already integrated into Arc, and the implementation costs are low when schemas are determined, making it pointless to Mock. However, some open source code is still listed for reference
- API Mock
- graphql-faker
- graphql-tools
- SDL
- graphql-editor
- voyager
- IDE
- graphql-playground
- graphiql
- graphqurl
7.2 Best Practices
- Specification spec.graphql.org/
- Tutorial: www.howtographql.com/
- Open API example github.com/APIs-guru/g…
- dgraph.io/tour