AnfÀnger zu sein bedeutet, neue Horizonte in der Programmierung zu erkunden, ins Unbekannte zu treten und zu hoffen, dass es irgendwo besser wird.
Ich denke, Sie werden mir zustimmen, dass es oft sehr lustig ist, ein Projekt mit neuer Technologie zu starten. Die Probleme, mit denen Sie konfrontiert sind und die Sie zu lösen versuchen, sind nicht immer einfach, obwohl sie ein wesentlicher Bestandteil Ihrer Reise zum Guru sind.
Also, wovon rede ich? Heute bin ich hier, um Ihnen meine ersten Erfahrungen mit der Erstellung eines Systems aus Hedless CMS, API und Blog mitzuteilen. Aufgrund des Mangels an ausreichender Menge solchen Materials, insbesondere auf Russisch, hoffe ich, dass dieser Artikel Ihnen dabei hilft, ein solches System selbst zu erstellen und die Fehler zu vermeiden, die ich gemacht habe.
Ich werde Ihnen erzĂ€hlen, wie ich das System in Blöcken zusammengebaut habe und was daraus entstanden ist. Ich werde die Hintergrundinformationen nicht erklĂ€ren, aber ich werde Links zu Ressourcen hinterlassen, in denen Sie mehr erfahren können. Manchmal ist es schwierig, eine russischsprachige Quelle zu finden, aber ich werde es versuchen. DarĂŒber hinaus können Sie den Vortrag (auf Englisch) ansehen oder diesen Artikel lesen (der am nĂ€chsten kommt), wenn Sie sich nicht sicher sind, welche Vorteile Microservices gegenĂŒber monolithischer Architektur haben.
API ( , ):
Vidzhel/Bluro
, , , - , - . .
( ) . , , , «» . .
, , . . - , .
, , . , Headless () CMS, Bluro. «Hello world» , «TechOverload» .
-, , , .
, . . . , , , , .
, :
, ,
, , ,
, ,
,
, ,
, , , , :
, , . , , . , , : , , Headless CMS, , .
- , Python Django. , , .
, YouTube, .
, , . â , URL (, ). - .
, , . , , .
API. - , .
JavaScript, NodeJS React . , .
Bluro CMS
Headless CMS , (UI). , . CMS API (REST API , ), .
, , , API â , â , . , , , , URL-, , .
, http . , , .
â MVC (Model View Controller). ( ).
, , , , .
CMS .
, - API, CMS. , , , , .
- , .
. .
Main , .
ORM
, â ORM (Object Relational Mapper).
, , , - ? , , . , . , â .
â «». , , SQL .
, , . : (, ), ( ), , . , , . , - « ».
. , Model ( ), . Model . , , .
, ORM. , .
. , , . . , , - . , , - , . , - : ).
, . Sequelize API Django, . ORM.
Entities â , ( , ). Model QuerySet , . , QuerySet Statement, API . StatementsBuilder â , Statement . , .
« », , .
, , . , , , ORM.
ORM. , .
const Model = DependencyResolver . getDependency ( null , "Model" ) ;
const ARTICLE_STATES = {
PUBLISHED : "PUBLISHED" ,
PENDING_PUBLISHING : "PENDING_PUBLISHING" ,
} ;
const VERBOSE_REGEXP = /^ [ 0-9a-z-._~] * $ /i ;
class Article extends Model {
static STATES = ARTICLE_STATES ;
// There can be other methods
// that fetch data for you or process it in some way
}
// Define model with schema
Article . init ( [
{
columnName : "user" ,
foreignKey : {
table : "User" ,
columnName : "id" ,
onDelete : Model . OP . CASCADE ,
onUpdate : Model . OP . CASCADE ,
} ,
type : Model . DATA_TYPES . INT ( ) ,
} ,
{
columnName : "dateOfPublishing" ,
verboseName : "Date of publishing" ,
type : Model . DATA_TYPES . DATE_TIME ( ) ,
nullable : true ,
validators : Model . CUSTOM_VALIDATORS_GENERATORS . dateInterval ( ) ,
} ,
{
columnName : "dateOfChanging" ,
verboseName : "Date of changing" ,
type : Model . DATA_TYPES . DATE_TIME ( ) ,
validators : Model . CUSTOM_VALIDATORS_GENERATORS . dateInterval ( ) ,
} ,
...
{
columnName : "state" ,
verboseName : "Article state" ,
type : Model . DATA_TYPES . VARCHAR ( 18 ) ,
possibleValues : Object . values ( ARTICLE_STATES ) ,
} ,
] ) ;
// Somewhere else
const set = await Article . selector
. orderBy ( { dateOfPublishing : "DESC" } )
. limit ( offset , count )
. filter ( {
firstValue : "dateOfChanging" ,
operator : Operators . between ,
innerCondition : {
firstValue : "10.11.2020" ,
operator : Operators . and ,
secondValue : "11.11.2020" ,
} ,
} )
. filter ( {
user : "userId" ,
state : Article . STATES . PUBLISHED ,
} )
. fetch ( ) ;
const resulte = await set . getList ( ) ;
. , .
, , . , . , , Django.
, CMS, , . , , , . , , . , . , , , .
GIT, , .
. , .
{
" migrated" : true ,
" initialMigration" : true ,
" tables" : [
[
" User" ,
{
" migrated" : false ,
" DEFINE_TABLE" : true ,
" DEFINE_COLUMN" : {
" userName" : {
" name" : " userName" ,
" type" : { " id" : " VARCHAR" , " size" : 10 },
" default" : null ,
" nullable" : false ,
" autoincrement" : false ,
" primaryKey" : false ,
" unique" : false ,
" foreignKey" : null
},
" password" : {
" name" : " password" ,
" type" : { " id" : " VARCHAR" , " size" : 10 },
" default" : null ,
" nullable" : false ,
" autoincrement" : false ,
" primaryKey" : false ,
" unique" : false ,
" foreignKey" : null
},
" id" : {
" name" : " id" ,
" type" : { " id" : " INT" },
" default" : null ,
" nullable" : false ,
" autoincrement" : true ,
" primaryKey" : true ,
" unique" : false ,
" foreignKey" : null
}
}
}
]
],
" name" : " 0_Auth_migration.json"
}
{
" migrated" : true ,
" initialMigration" : false ,
" tables" : [
[
" User" ,
{
" migrated" : false ,
" CHANGE_COLUMN" : {
" password" : {
" name" : " password" ,
" type" : {
" id" : " VARCHAR" ,
" size" : 50
},
" default" : null ,
" nullable" : false ,
" autoincrement" : false ,
" primaryKey" : false ,
" unique" : false ,
" foreignKey" : null
}
}
}
]
],
" name" : " 1_Auth_migration.json"
}
Server
, http-. , , . HTTP, : Request Response, .
Request , , multipart / form-data.
Response , . , cookie.
Router
«» â , . Express â , , , .
Route â , . , , . .
Rule â , . , authorizationRule, , . , . , . Rule , , Rule Route.
. , ( ), .
connectRule ( "all" , "/" , authRule , { sensitive : false } ) ;
connectRule ( [ "put" , "delete" ] , "/profiles/{verbose}" , requireAuthorizationRule ) ;
connectRule ( [ "post" , "delete" ] , "/profiles/{user}/followers" , requireAuthorizationRule ) ;
connectRoute ( "get" , "/profiles" , getProfilesController ) ;
connectRoute ( "put" , "/profiles/{verbose}" , updateProfileController ) ;
, . , API. , , , , .
, API, , . , , , .
. â - , . Modules Manager, , , , . , .
, SOLID, . , , . , , . - , .
.
API
, API . , , . , , .
, , : , , , ...
API , . , , .
, JWT (JSON Web Token) cookie. . .
, :
authRule â , cookie . , , .
requireAuthorizationRule â , .
, . , .
.
.
NotificationService .
API:
{
" email" : " email" ,
" pass" : " password"
}
{
" session" : {
" verbose" : " id that is used to get profile info" ,
" userName" : " userName" ,
" role" : " user role: 'ADMIN', 'USER'" ,
" email" : " email"
},
" errors" : " error's descriptions list" ,
" success" : " success's descriptions list" ,
" info" : " info's descriptions list" ,
" notifications" : [
" collection of notifications"
]
}
CMS, , , . React .
- , . « » . React Router . , -. , , , -, .
Redux "" Redux-Saga ( Redux-Saga ). , Redux (Action), . (Reducer) , - , , .
, Redux-Saga , , . , .
Redux-Saga, Headless CMS. , :
function * fetchData ( endpoint , requestData ) {
const controller = new AbortController ( ) ;
const { signal } = controller ;
let res , wasTimeout , reason , failure ;
failure = false ;
try {
// use Fetch API to make request, wait no longer than `TIMEOUT`
const raceRes = yield race ( [
call ( fetch , endpoint , {
...requestData ,
signal,
mode : "cors" ,
redirect : "follow" ,
credentials : "include" ,
} ) ,
delay ( TIMEOUT , true ) ,
] ) ;
res = raceRes [ 0 ] ;
wasTimeout = raceRes [ 1 ] || false ;
if ( wasTimeout ) {
failure = true ;
reason = "Connection timeout" ;
// Abort fetching
controller . abort ( ) ;
}
} catch ( e ) {
console . log ( e ) ;
reason = "Error occurred" ;
}
return { reason, res, failure, wasTimeout } ;
}
export function * makeRequest ( endpoint , requestData ) {
// Signal that we start making request (we can use it to show loading wheel)
yield put ( { type : SES_ASYNC . START_MAKING_REQUEST_ASYNC } ) ;
// call enother saga that will make request
let { res, reason, failure, wasTimeout } = yield call ( fetchData , endpoint , requestData ) ;
if ( res ) {
// Process response
const results = yield call ( handleResponse , res , wasTimeout , reason , failure ) ;
// Signal about finishing
yield put ( { type : SES_ASYNC . END_MAKING_REQUEST_ASYNC } ) ;
return results ;
} else {
// Return error
failure = true ;
reason = "Server error" ;
yield put ( { type : SES_ASYNC . END_MAKING_REQUEST_ASYNC } ) ;
return { res : null , wasTimeout, reason, data : null , failure } ;
}
}
fetchData â , Fetch API . , TIMEOUT, . makeRequest , . - . , , :
function * openArticle ( { verbose } ) {
// Get cached articles from the state
const article = yield select ( getFetchedArticle , verbose ) ;
// If we don't have this article in cache, fetch it
if ( !article ) {
const { failure, data } = yield call (
makeRequest ,
`${ configs . endpoints . articles } /${ verbose } ` ,
{
method : "GET" ,
} ,
) ;
if ( !failure ) {
article = yield call ( convertArticleData , data . entry ) ;
}
}
// If article was successfuly fetched, we signaling to open it
if ( article ) {
yield fork ( fetchArticleContent , { fileName : article . textSourceName } ) ;
yield put ( {
type : ART_ASYNC . OPEN_ARTICLE_ASYNC ,
article,
} ) ;
}
}
, . ( ).
. â .
- NGINX:
server {
listen 80;
client_max_body_size 100M;
location / {
proxy_pass http://front_blog:3000;
}
location /admin {
proxy_pass http://front_admin_panel:3000;
}
location /api {
rewrite ^/api/?(.*)$ /$1 break;
proxy_pass http://bluro_api:8000;
}
}
Docker Compose, . , ( â ).
- , headlesscms.org , Headless CMS , .
, , , -.