fkl_mir/strategy/
context_map.rs

1use std::fmt::Display;
2use serde::Deserialize;
3use serde::Serialize;
4
5use crate::{BoundedContext, ConnectionDirection, ContextRelation, LayeredArchitecture, SourceSets, Step};
6use crate::environment::Environment;
7use crate::implementation::Implementation;
8
9///
10/// Identify each model in play on the project and define its bounded context. This includes
11/// the implicit models of non-object-oriented subsystems. Name each bounded context, and make
12/// the names part of the ubiquitous language.
13///
14/// Describe the points of contact between the models, outlining explicit translation for
15/// any communication, highlighting any sharing, isolation mechanisms, and levels of influence.
16///
17/// Map the existing terrain. Take up transformations later.
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
19pub struct ContextMap {
20  pub name: String,
21  pub state: ContextState,
22  pub contexts: Vec<BoundedContext>,
23  pub relations: Vec<ContextRelation>,
24  pub implementations: Vec<Implementation>,
25  pub layered: Option<LayeredArchitecture>,
26  pub source_sets: Option<SourceSets>,
27  pub envs: Vec<Environment>
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31pub enum ContextState {
32  AsIs,
33  ToBe,
34}
35
36impl Default for ContextState {
37  fn default() -> Self {
38    ContextState::ToBe
39  }
40}
41
42impl Display for ContextMap {
43  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44    writeln!(f, "ContextMap({})", self.name)?;
45    for relation in &self.relations {
46      let rel = match relation.connection_type {
47        ConnectionDirection::Undirected => "-",
48        ConnectionDirection::PositiveDirected => "->",
49        ConnectionDirection::NegativeDirected => "<-",
50        ConnectionDirection::BiDirected => "<->",
51      };
52      writeln!(f, "  Relation({} {} {}) ", relation.source, rel, relation.target)?;
53    }
54
55    for context in &self.contexts {
56      writeln!(f, "  BoundedContext({})", context.name)?;
57      for aggregate in &context.aggregates {
58        writeln!(f, "    Aggregate({})", aggregate.name)?;
59        for entity in &aggregate.entities {
60          writeln!(f, "      Entity({})", entity.name)?;
61          entity.fields.iter().for_each(|field| {
62            writeln!(f, "        Field({})", field.name).unwrap();
63          });
64        }
65      }
66    }
67
68    for imp in &self.implementations {
69      match imp {
70        Implementation::PublishHttpApi(api) => {
71          writeln!(f, "    PublishHttpApi({})", api.name)?;
72          writeln!(f, "      {:?} Path({})", api.endpoint.method, api.endpoint.path)?;
73
74          if let Some(request) = &api.endpoint.request {
75            writeln!(f, "      Request: {}", request.name)?;
76          }
77
78          if let Some(response) = &api.endpoint.response {
79            writeln!(f, "      Response: {}", response.name)?;
80          }
81
82          api.flow.iter().for_each(|flow| {
83            writeln!(f, "      Flow").unwrap();
84            flow.steps.iter().for_each(|step| {
85              match step {
86                Step::MethodCall(call) => {
87                  writeln!(f, "        MethodCall({})", call.name).unwrap();
88                }
89                Step::Message(msg) => {
90                  writeln!(f, "        Message({})", msg.from).unwrap();
91                }
92                Step::RpcCall(_) => {}
93              }
94            });
95          });
96        }
97        Implementation::PublishEvent => {}
98        Implementation::PublishMessage => {}
99      }
100    }
101
102    self.layered.as_ref().map(|layered| {
103      writeln!(f, "  LayeredArchitecture({})", layered.name).unwrap();
104      for layer in &layered.layers {
105        writeln!(f, "    Layer {} (\"{}\")", layer.name, layer.package).unwrap();
106      }
107    });
108
109    Ok(())
110  }
111}
112
113#[cfg(test)]
114mod tests {
115  use crate::ContextMap;
116  use crate::{Aggregate, BoundedContext, Entity};
117
118  #[test]
119  fn display_context_map() {
120    let context_map = ContextMap {
121      name: "Ticket".to_string(),
122      state: Default::default(),
123      contexts: vec![BoundedContext {
124        name: "TicketContext".to_string(),
125        aggregates: vec![
126          Aggregate {
127            name: "TicketAggregate".to_string(),
128            description: "".to_string(),
129            entities: vec![Entity {
130              name: "TicketEntity".to_string(),
131              description: "".to_string(),
132              is_aggregate_root: false,
133              identify: Default::default(),
134              fields: vec![],
135            }],
136          }
137        ],
138      }],
139      relations: vec![],
140      implementations: vec![],
141      layered: None,
142      source_sets: None,
143      envs: vec![]
144    };
145    let output = format!("{}", context_map);
146    assert_eq!(output, r#"ContextMap(Ticket)
147  BoundedContext(TicketContext)
148    Aggregate(TicketAggregate)
149      Entity(TicketEntity)
150"#);
151  }
152}